[gnumeric] Criteria: move the criteria code to separate file.
- From: Morten Welinder <mortenw src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnumeric] Criteria: move the criteria code to separate file.
- Date: Mon, 8 Aug 2016 15:16:17 +0000 (UTC)
commit 4732d524e79eedd28ef853464a1dd8908de249f3
Author: Morten Welinder <terra gnome org>
Date: Mon Aug 8 11:14:55 2016 -0400
Criteria: move the criteria code to separate file.
This code is used by database functions (DSUM, etc), "if" functions
(SUMIF, etc.) and well as the sheet filter code.
plugins/fn-database/functions.c | 1 +
plugins/fn-math/functions.c | 1 +
src/Makefile.am | 1 +
src/criteria.c | 571 +++++++++++++++++++++++++++++++++++++++
src/criteria.h | 54 ++++
src/tools/filter.c | 1 +
src/value.c | 570 --------------------------------------
src/value.h | 33 ---
8 files changed, 629 insertions(+), 603 deletions(-)
---
diff --git a/plugins/fn-database/functions.c b/plugins/fn-database/functions.c
index 10ea841..ec62a46 100644
--- a/plugins/fn-database/functions.c
+++ b/plugins/fn-database/functions.c
@@ -30,6 +30,7 @@
#include <sheet.h>
#include <workbook.h>
#include <value.h>
+#include <criteria.h>
#include <collect.h>
#include <rangefunc.h>
#include <gnm-i18n.h>
diff --git a/plugins/fn-math/functions.c b/plugins/fn-math/functions.c
index a640ea5..3ae56cc 100644
--- a/plugins/fn-math/functions.c
+++ b/plugins/fn-math/functions.c
@@ -33,6 +33,7 @@
#include <rangefunc.h>
#include <collect.h>
#include <value.h>
+#include <criteria.h>
#include <expr.h>
#include <position.h>
#include <regression.h>
diff --git a/src/Makefile.am b/src/Makefile.am
index 4a82411..29f5ef3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -108,6 +108,7 @@ libspreadsheet_la_SOURCES = \
cell-draw.c \
cellspan.c \
clipboard.c \
+ criteria.c \
cmd-edit.c \
collect.c \
colrow.c \
diff --git a/src/criteria.c b/src/criteria.c
new file mode 100644
index 0000000..0a8acaf
--- /dev/null
+++ b/src/criteria.c
@@ -0,0 +1,571 @@
+#include "criteria.h"
+
+typedef enum { CRIT_NULL, CRIT_FLOAT, CRIT_WRONGTYPE, CRIT_STRING } CritType;
+
+static CritType
+criteria_inspect_values (GnmValue const *x, gnm_float *xr, gnm_float *yr,
+ GnmCriteria *crit)
+{
+ GnmValue *vx;
+ GnmValue const *y = crit->x;
+
+ if (x == NULL || y == NULL)
+ return CRIT_NULL;
+
+ switch (y->v_any.type) {
+ case VALUE_BOOLEAN:
+ /* If we're searching for a bool -- even one that is
+ from a string search value -- we match only bools. */
+ if (!VALUE_IS_BOOLEAN (x))
+ return CRIT_WRONGTYPE;
+ *xr = value_get_as_float (x);
+ *yr = value_get_as_float (y);
+ return CRIT_FLOAT;
+
+ case VALUE_EMPTY:
+ return CRIT_WRONGTYPE;
+
+ case VALUE_STRING:
+ if (!VALUE_IS_STRING (x))
+ return CRIT_WRONGTYPE;
+ return CRIT_STRING;
+
+ default:
+ g_warning ("This should not happen. Please report.");
+ return CRIT_WRONGTYPE;
+
+ case VALUE_FLOAT:
+ *yr = value_get_as_float (y);
+
+ if (VALUE_IS_BOOLEAN (x) || VALUE_IS_ERROR (x))
+ return CRIT_WRONGTYPE;
+ else if (VALUE_IS_FLOAT (x)) {
+ *xr = value_get_as_float (x);
+ return CRIT_FLOAT;
+ }
+
+ vx = format_match (value_peek_string (x), NULL, crit->date_conv);
+ if (VALUE_IS_EMPTY (vx) ||
+ VALUE_IS_BOOLEAN (y) != VALUE_IS_BOOLEAN (vx)) {
+ value_release (vx);
+ return CRIT_WRONGTYPE;
+ }
+
+ *xr = value_get_as_float (vx);
+ value_release (vx);
+ return CRIT_FLOAT;
+ }
+}
+
+
+static gboolean
+criteria_test_equal (GnmValue const *x, GnmCriteria *crit)
+{
+ gnm_float xf, yf;
+ GnmValue const *y = crit->x;
+
+ switch (criteria_inspect_values (x, &xf, &yf, crit)) {
+ default:
+ g_assert_not_reached ();
+ case CRIT_NULL:
+ case CRIT_WRONGTYPE:
+ return FALSE;
+ case CRIT_FLOAT:
+ return xf == yf;
+ case CRIT_STRING:
+ /* FIXME: _ascii_??? */
+ return g_ascii_strcasecmp (value_peek_string (x),
+ value_peek_string (y)) == 0;
+ }
+}
+
+static gboolean
+criteria_test_unequal (GnmValue const *x, GnmCriteria *crit)
+{
+ gnm_float xf, yf;
+
+ switch (criteria_inspect_values (x, &xf, &yf, crit)) {
+ default:
+ g_assert_not_reached ();
+ case CRIT_NULL:
+ case CRIT_WRONGTYPE:
+ return TRUE;
+ case CRIT_FLOAT:
+ return xf != yf;
+ case CRIT_STRING:
+ /* FIXME: _ascii_??? */
+ return g_ascii_strcasecmp (value_peek_string (x),
+ value_peek_string (crit->x)) != 0;
+ }
+}
+
+static gboolean
+criteria_test_less (GnmValue const *x, GnmCriteria *crit)
+{
+ gnm_float xf, yf;
+ GnmValue const *y = crit->x;
+
+ switch (criteria_inspect_values (x, &xf, &yf, crit)) {
+ default:
+ g_assert_not_reached ();
+ case CRIT_NULL:
+ case CRIT_WRONGTYPE:
+ return FALSE;
+ case CRIT_STRING:
+ return go_utf8_collate_casefold (value_peek_string (x),
+ value_peek_string (y)) < 0;
+ case CRIT_FLOAT:
+ return xf < yf;
+ }
+}
+
+static gboolean
+criteria_test_greater (GnmValue const *x, GnmCriteria *crit)
+{
+ gnm_float xf, yf;
+ GnmValue const *y = crit->x;
+
+ switch (criteria_inspect_values (x, &xf, &yf, crit)) {
+ default:
+ g_assert_not_reached ();
+ case CRIT_NULL:
+ case CRIT_WRONGTYPE:
+ return FALSE;
+ case CRIT_STRING:
+ return go_utf8_collate_casefold (value_peek_string (x),
+ value_peek_string (y)) > 0;
+ case CRIT_FLOAT:
+ return xf > yf;
+ }
+}
+
+static gboolean
+criteria_test_less_or_equal (GnmValue const *x, GnmCriteria *crit)
+{
+ gnm_float xf, yf;
+ GnmValue const *y = crit->x;
+
+ switch (criteria_inspect_values (x, &xf, &yf, crit)) {
+ default:
+ g_assert_not_reached ();
+ case CRIT_NULL:
+ case CRIT_WRONGTYPE:
+ return FALSE;
+ case CRIT_STRING:
+ return go_utf8_collate_casefold (value_peek_string (x),
+ value_peek_string (y)) <= 0;
+ case CRIT_FLOAT:
+ return xf <= yf;
+ }
+}
+
+static gboolean
+criteria_test_greater_or_equal (GnmValue const *x, GnmCriteria *crit)
+{
+ gnm_float xf, yf;
+ GnmValue const *y = crit->x;
+
+ switch (criteria_inspect_values (x, &xf, &yf, crit)) {
+ default:
+ g_assert_not_reached ();
+ case CRIT_NULL:
+ case CRIT_WRONGTYPE:
+ return FALSE;
+ case CRIT_STRING:
+ return go_utf8_collate_casefold (value_peek_string (x),
+ value_peek_string (y)) >= 0;
+ case CRIT_FLOAT:
+ return xf >= yf;
+ }
+}
+
+static gboolean
+criteria_test_match (GnmValue const *x, GnmCriteria *crit)
+{
+ if (!crit->has_rx)
+ return FALSE;
+
+ // Only strings are matched
+ if (!VALUE_IS_STRING (x))
+ return FALSE;
+
+ return go_regexec (&crit->rx, value_peek_string (x), 0, NULL, 0) ==
+ GO_REG_OK;
+}
+
+static gboolean
+criteria_test_empty (GnmValue const *x, GnmCriteria *crit)
+{
+ return VALUE_IS_EMPTY (x);
+}
+
+static gboolean
+criteria_test_nonempty (GnmValue const *x, GnmCriteria *crit)
+{
+ return !VALUE_IS_EMPTY (x);
+}
+
+/*
+ * Finds a column index of a field.
+ */
+int
+find_column_of_field (GnmEvalPos const *ep,
+ GnmValue const *database, GnmValue const *field)
+{
+ Sheet *sheet;
+ GnmCell *cell;
+ gchar *field_name;
+ int begin_col, end_col, row, n, column;
+ int offset;
+
+ // I'm not certain we should demand this, but the code clearly wants
+ // it.
+ if (!VALUE_IS_CELLRANGE (database))
+ return -1;
+
+ offset = database->v_range.cell.a.col;
+
+ if (VALUE_IS_FLOAT (field))
+ return value_get_as_int (field) + offset - 1;
+
+ if (!VALUE_IS_STRING (field))
+ 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;
+ gnm_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;
+}
+
+void
+free_criteria (GnmCriteria *criteria)
+{
+ if (!criteria || criteria->ref_count-- > 1)
+ return;
+ value_release (criteria->x);
+ if (criteria->has_rx)
+ go_regfree (&criteria->rx);
+ g_free (criteria);
+}
+
+static GnmCriteria *
+gnm_criteria_ref (GnmCriteria *criteria)
+{
+ criteria->ref_count++;
+ return criteria;
+}
+
+GType
+gnm_criteria_get_type (void)
+{
+ static GType t = 0;
+
+ if (t == 0) {
+ t = g_boxed_type_register_static ("GnmCriteria",
+ (GBoxedCopyFunc)gnm_criteria_ref,
+ (GBoxedFreeFunc)free_criteria);
+ }
+ return t;
+}
+
+/**
+ * free_criterias:
+ * @criterias: (element-type GnmCriteria) (transfer full): the criteria to be
+ * freed.
+ * Frees the allocated memory.
+ */
+void
+free_criterias (GSList *criterias)
+{
+ GSList *list = criterias;
+
+ while (criterias != NULL) {
+ GnmDBCriteria *criteria = criterias->data;
+ g_slist_free_full (criteria->conditions,
+ (GFreeFunc)free_criteria);
+ g_free (criteria);
+ criterias = criterias->next;
+ }
+ g_slist_free (list);
+}
+
+/**
+ * parse_criteria:
+ * @crit_val: #GnmValue
+ * @date_conv: #GODateConventions
+ *
+ * Returns: (transfer full): GnmCriteria which caller must free.
+ *
+ * ">=value"
+ * "<=value"
+ * "<>value"
+ * "<value"
+ * ">value"
+ * "=value"
+ * "pattern"
+ **/
+GnmCriteria *
+parse_criteria (GnmValue const *crit_val, GODateConventions const *date_conv,
+ gboolean anchor_end)
+{
+ int len;
+ char const *criteria;
+ GnmCriteria *res = g_new0 (GnmCriteria, 1);
+ GnmValue *empty;
+
+ res->iter_flags = CELL_ITER_IGNORE_BLANK;
+ res->date_conv = date_conv;
+
+ if (VALUE_IS_NUMBER (crit_val)) {
+ res->fun = criteria_test_equal;
+ res->x = value_dup (crit_val);
+ return res;
+ }
+
+ criteria = value_peek_string (crit_val);
+ if (strncmp (criteria, "<=", 2) == 0) {
+ res->fun = criteria_test_less_or_equal;
+ len = 2;
+ } else if (strncmp (criteria, ">=", 2) == 0) {
+ res->fun = criteria_test_greater_or_equal;
+ len = 2;
+ } else if (strncmp (criteria, "<>", 2) == 0) {
+ /* "<>" by itself is special: */
+ res->fun = (criteria[2] == 0) ? criteria_test_nonempty : criteria_test_unequal;
+ len = 2;
+ } else if (*criteria == '<') {
+ res->fun = criteria_test_less;
+ len = 1;
+ } else if (*criteria == '=') {
+ /* "=" by itself is special: */
+ res->fun = (criteria[1] == 0) ? criteria_test_empty : criteria_test_equal;
+ len = 1;
+ } else if (*criteria == '>') {
+ res->fun = criteria_test_greater;
+ len = 1;
+ } else {
+ res->fun = criteria_test_match;
+ res->has_rx = (gnm_regcomp_XL (&res->rx, criteria, GO_REG_ICASE, TRUE, anchor_end) ==
GO_REG_OK);
+ len = 0;
+ }
+
+ res->x = format_match_number (criteria + len, NULL, date_conv);
+ if (res->x == NULL)
+ res->x = value_new_string (criteria + len);
+ else if (len == 0 && VALUE_IS_NUMBER (res->x))
+ res->fun = criteria_test_equal;
+
+ empty = value_new_empty ();
+ if (res->fun (empty, res))
+ res->iter_flags &= ~CELL_ITER_IGNORE_BLANK;
+ value_release (empty);
+ res->ref_count = 1;
+
+ return res;
+}
+
+
+static GSList *
+parse_criteria_range (Sheet *sheet, int b_col, int b_row, int e_col, int e_row,
+ int *field_ind, gboolean anchor_end)
+{
+ GSList *criterias = NULL;
+ GODateConventions const *date_conv =
+ workbook_date_conv (sheet->workbook);
+ int i, j;
+
+ for (i = b_row; i <= e_row; i++) {
+ GnmDBCriteria *new_criteria = g_new (GnmDBCriteria, 1);
+ GSList *conditions = NULL;
+
+ for (j = b_col; j <= e_col; j++) {
+ GnmCriteria *cond;
+ GnmCell *cell = sheet_cell_get (sheet, j, i);
+ if (cell != NULL)
+ gnm_cell_eval (cell);
+ if (gnm_cell_is_empty (cell))
+ continue;
+
+ cond = parse_criteria (cell->value, date_conv,
+ anchor_end);
+ cond->column = (field_ind != NULL)
+ ? field_ind[j - b_col]
+ : j - b_col;
+ conditions = g_slist_prepend (conditions, cond);
+ }
+
+ new_criteria->conditions = g_slist_reverse (conditions);
+ criterias = g_slist_prepend (criterias, new_criteria);
+ }
+
+ return g_slist_reverse (criterias);
+}
+
+/**
+ * parse_database_criteria:
+ * @ep: #GnmEvalPos
+ * @database: #GnmValue
+ * @criteria: #GnmValue
+ *
+ * Parses the criteria cell range.
+ * Returns: (element-type GnmDBCriteria) (transfer full):
+ */
+GSList *
+parse_database_criteria (GnmEvalPos const *ep, GnmValue const *database, GnmValue const *criteria)
+{
+ Sheet *sheet;
+ GnmCell *cell;
+ int i;
+ int b_col, b_row, e_col, e_row;
+ int *field_ind;
+
+ g_return_val_if_fail (VALUE_IS_CELLRANGE (criteria), NULL);
+
+ 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;
+
+ if (e_col < b_col) {
+ int tmp = b_col;
+ b_col = e_col;
+ e_col = tmp;
+ }
+
+ /* 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;
+ gnm_cell_eval (cell);
+ if (gnm_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,
+ FALSE);
+}
+
+/**
+ * find_rows_that_match:
+ * @sheet: #Sheet
+ * @first_col: first column.
+ * @first_row: first row.
+ * @last_col: last column.
+ * @last_row: laset row.
+ * @criterias: (element-type GnmDBCriteria): the criteria to use.
+ * @unique_only:
+ *
+ * Finds the rows from the given database that match the criteria.
+ * Returns: (element-type int) (transfer full): the list of matching rows.
+ **/
+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 *rows = NULL;
+ GSList const *crit_ptr, *cond_ptr;
+ int row;
+ gboolean add_flag;
+ char const *t1, *t2;
+ GnmCell *test_cell;
+ GnmValue const *empty = value_new_empty ();
+
+ for (row = first_row; row <= last_row; row++) {
+ add_flag = TRUE;
+ for (crit_ptr = criterias; crit_ptr; crit_ptr = crit_ptr->next) {
+ GnmDBCriteria const *crit = crit_ptr->data;
+ add_flag = TRUE;
+ for (cond_ptr = crit->conditions;
+ cond_ptr != NULL ; cond_ptr = cond_ptr->next) {
+ GnmCriteria *cond = cond_ptr->data;
+ test_cell = sheet_cell_get (sheet, cond->column, row);
+ if (test_cell != NULL)
+ gnm_cell_eval (test_cell);
+ if (!cond->fun (test_cell ? test_cell->value : empty, cond)) {
+ add_flag = FALSE;
+ break;
+ }
+ }
+
+ 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++) {
+ test_cell = sheet_cell_get (sheet, i, trow);
+ cell = sheet_cell_get (sheet, i, row);
+
+ /* FIXME: this is probably not right, but crashing is more
wrong. */
+ if (test_cell == NULL || cell == NULL)
+ continue;
+
+ 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);
+}
+
+/****************************************************************************/
diff --git a/src/criteria.h b/src/criteria.h
new file mode 100644
index 0000000..caab0ae
--- /dev/null
+++ b/src/criteria.h
@@ -0,0 +1,54 @@
+#ifndef _GNM_CRITERIA_H_
+#define _GNM_CRITERIA_H_
+
+#include <gnumeric.h>
+#include <position.h>
+#include <value.h>
+#include <number-match.h>
+#include <sheet.h>
+#include <cell.h>
+#include <gutils.h>
+#include <workbook.h>
+#include <goffice/goffice.h>
+
+#include <string.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GnmCriteria GnmCriteria;
+
+typedef gboolean (*GnmCriteriaFunc) (GnmValue const *x, GnmCriteria *crit);
+struct _GnmCriteria {
+ GnmCriteriaFunc fun;
+ GnmValue *x;
+ int column; /* absolute */
+ CellIterFlags iter_flags;
+ GODateConventions const *date_conv;
+ GORegexp rx;
+ gboolean has_rx;
+ unsigned ref_count; /* for boxed type */
+};
+GType gnm_criteria_get_type (void);
+
+typedef struct {
+ int row; /* absolute */
+ GSList *conditions;
+} GnmDBCriteria;
+
+GnmCriteria *parse_criteria (GnmValue const *crit_val,
+ GODateConventions const *date_conv,
+ gboolean anchor_end);
+void free_criteria (GnmCriteria *criteria);
+void free_criterias (GSList *criterias);
+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 *parse_database_criteria (GnmEvalPos const *ep,
+ GnmValue const *database, GnmValue const *criteria);
+int find_column_of_field (GnmEvalPos const *ep,
+ GnmValue const *database, GnmValue const *field);
+
+
+G_END_DECLS
+
+#endif
diff --git a/src/tools/filter.c b/src/tools/filter.c
index 0effd72..824781c 100644
--- a/src/tools/filter.c
+++ b/src/tools/filter.c
@@ -34,6 +34,7 @@
#include <ranges.h>
#include <value.h>
#include <selection.h>
+#include <criteria.h>
#include "filter.h"
#include "analysis-tools.h"
diff --git a/src/value.c b/src/value.c
index 03a4127..9f52201 100644
--- a/src/value.c
+++ b/src/value.c
@@ -1478,576 +1478,6 @@ value_set_fmt (GnmValue *v, GOFormat const *fmt)
/****************************************************************************/
-typedef enum { CRIT_NULL, CRIT_FLOAT, CRIT_WRONGTYPE, CRIT_STRING } CritType;
-
-static CritType
-criteria_inspect_values (GnmValue const *x, gnm_float *xr, gnm_float *yr,
- GnmCriteria *crit)
-{
- GnmValue *vx;
- GnmValue const *y = crit->x;
-
- if (x == NULL || y == NULL)
- return CRIT_NULL;
-
- switch (y->v_any.type) {
- case VALUE_BOOLEAN:
- /* If we're searching for a bool -- even one that is
- from a string search value -- we match only bools. */
- if (!VALUE_IS_BOOLEAN (x))
- return CRIT_WRONGTYPE;
- *xr = value_get_as_float (x);
- *yr = value_get_as_float (y);
- return CRIT_FLOAT;
-
- case VALUE_EMPTY:
- return CRIT_WRONGTYPE;
-
- case VALUE_STRING:
- if (!VALUE_IS_STRING (x))
- return CRIT_WRONGTYPE;
- return CRIT_STRING;
-
- default:
- g_warning ("This should not happen. Please report.");
- return CRIT_WRONGTYPE;
-
- case VALUE_FLOAT:
- *yr = value_get_as_float (y);
-
- if (VALUE_IS_BOOLEAN (x) || VALUE_IS_ERROR (x))
- return CRIT_WRONGTYPE;
- else if (VALUE_IS_FLOAT (x)) {
- *xr = value_get_as_float (x);
- return CRIT_FLOAT;
- }
-
- vx = format_match (value_peek_string (x), NULL, crit->date_conv);
- if (VALUE_IS_EMPTY (vx) ||
- VALUE_IS_BOOLEAN (y) != VALUE_IS_BOOLEAN (vx)) {
- value_release (vx);
- return CRIT_WRONGTYPE;
- }
-
- *xr = value_get_as_float (vx);
- value_release (vx);
- return CRIT_FLOAT;
- }
-}
-
-
-static gboolean
-criteria_test_equal (GnmValue const *x, GnmCriteria *crit)
-{
- gnm_float xf, yf;
- GnmValue const *y = crit->x;
-
- switch (criteria_inspect_values (x, &xf, &yf, crit)) {
- default:
- g_assert_not_reached ();
- case CRIT_NULL:
- case CRIT_WRONGTYPE:
- return FALSE;
- case CRIT_FLOAT:
- return xf == yf;
- case CRIT_STRING:
- /* FIXME: _ascii_??? */
- return g_ascii_strcasecmp (value_peek_string (x),
- value_peek_string (y)) == 0;
- }
-}
-
-static gboolean
-criteria_test_unequal (GnmValue const *x, GnmCriteria *crit)
-{
- gnm_float xf, yf;
-
- switch (criteria_inspect_values (x, &xf, &yf, crit)) {
- default:
- g_assert_not_reached ();
- case CRIT_NULL:
- case CRIT_WRONGTYPE:
- return TRUE;
- case CRIT_FLOAT:
- return xf != yf;
- case CRIT_STRING:
- /* FIXME: _ascii_??? */
- return g_ascii_strcasecmp (value_peek_string (x),
- value_peek_string (crit->x)) != 0;
- }
-}
-
-static gboolean
-criteria_test_less (GnmValue const *x, GnmCriteria *crit)
-{
- gnm_float xf, yf;
- GnmValue const *y = crit->x;
-
- switch (criteria_inspect_values (x, &xf, &yf, crit)) {
- default:
- g_assert_not_reached ();
- case CRIT_NULL:
- case CRIT_WRONGTYPE:
- return FALSE;
- case CRIT_STRING:
- return go_utf8_collate_casefold (value_peek_string (x),
- value_peek_string (y)) < 0;
- case CRIT_FLOAT:
- return xf < yf;
- }
-}
-
-static gboolean
-criteria_test_greater (GnmValue const *x, GnmCriteria *crit)
-{
- gnm_float xf, yf;
- GnmValue const *y = crit->x;
-
- switch (criteria_inspect_values (x, &xf, &yf, crit)) {
- default:
- g_assert_not_reached ();
- case CRIT_NULL:
- case CRIT_WRONGTYPE:
- return FALSE;
- case CRIT_STRING:
- return go_utf8_collate_casefold (value_peek_string (x),
- value_peek_string (y)) > 0;
- case CRIT_FLOAT:
- return xf > yf;
- }
-}
-
-static gboolean
-criteria_test_less_or_equal (GnmValue const *x, GnmCriteria *crit)
-{
- gnm_float xf, yf;
- GnmValue const *y = crit->x;
-
- switch (criteria_inspect_values (x, &xf, &yf, crit)) {
- default:
- g_assert_not_reached ();
- case CRIT_NULL:
- case CRIT_WRONGTYPE:
- return FALSE;
- case CRIT_STRING:
- return go_utf8_collate_casefold (value_peek_string (x),
- value_peek_string (y)) <= 0;
- case CRIT_FLOAT:
- return xf <= yf;
- }
-}
-
-static gboolean
-criteria_test_greater_or_equal (GnmValue const *x, GnmCriteria *crit)
-{
- gnm_float xf, yf;
- GnmValue const *y = crit->x;
-
- switch (criteria_inspect_values (x, &xf, &yf, crit)) {
- default:
- g_assert_not_reached ();
- case CRIT_NULL:
- case CRIT_WRONGTYPE:
- return FALSE;
- case CRIT_STRING:
- return go_utf8_collate_casefold (value_peek_string (x),
- value_peek_string (y)) >= 0;
- case CRIT_FLOAT:
- return xf >= yf;
- }
-}
-
-static gboolean
-criteria_test_match (GnmValue const *x, GnmCriteria *crit)
-{
- if (!crit->has_rx)
- return FALSE;
-
- // Only strings are matched
- if (!VALUE_IS_STRING (x))
- return FALSE;
-
- return go_regexec (&crit->rx, value_peek_string (x), 0, NULL, 0) ==
- GO_REG_OK;
-}
-
-static gboolean
-criteria_test_empty (GnmValue const *x, GnmCriteria *crit)
-{
- return VALUE_IS_EMPTY (x);
-}
-
-static gboolean
-criteria_test_nonempty (GnmValue const *x, GnmCriteria *crit)
-{
- return !VALUE_IS_EMPTY (x);
-}
-
-/*
- * Finds a column index of a field.
- */
-int
-find_column_of_field (GnmEvalPos const *ep,
- GnmValue const *database, GnmValue const *field)
-{
- Sheet *sheet;
- GnmCell *cell;
- gchar *field_name;
- int begin_col, end_col, row, n, column;
- int offset;
-
- // I'm not certain we should demand this, but the code clearly wants
- // it.
- if (!VALUE_IS_CELLRANGE (database))
- return -1;
-
- offset = database->v_range.cell.a.col;
-
- if (VALUE_IS_FLOAT (field))
- return value_get_as_int (field) + offset - 1;
-
- if (!VALUE_IS_STRING (field))
- 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;
- gnm_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;
-}
-
-void
-free_criteria (GnmCriteria *criteria)
-{
- if (!criteria || criteria->ref_count-- > 1)
- return;
- value_release (criteria->x);
- if (criteria->has_rx)
- go_regfree (&criteria->rx);
- g_free (criteria);
-}
-
-static GnmCriteria *
-gnm_criteria_ref (GnmCriteria *criteria)
-{
- criteria->ref_count++;
- return criteria;
-}
-
-GType
-gnm_criteria_get_type (void)
-{
- static GType t = 0;
-
- if (t == 0) {
- t = g_boxed_type_register_static ("GnmCriteria",
- (GBoxedCopyFunc)gnm_criteria_ref,
- (GBoxedFreeFunc)free_criteria);
- }
- return t;
-}
-
-/**
- * free_criterias:
- * @criterias: (element-type GnmCriteria) (transfer full): the criteria to be
- * freed.
- * Frees the allocated memory.
- */
-void
-free_criterias (GSList *criterias)
-{
- GSList *list = criterias;
-
- while (criterias != NULL) {
- GnmDBCriteria *criteria = criterias->data;
- g_slist_free_full (criteria->conditions,
- (GFreeFunc)free_criteria);
- g_free (criteria);
- criterias = criterias->next;
- }
- g_slist_free (list);
-}
-
-/**
- * parse_criteria:
- * @crit_val: #GnmValue
- * @date_conv: #GODateConventions
- *
- * Returns: (transfer full): GnmCriteria which caller must free.
- *
- * ">=value"
- * "<=value"
- * "<>value"
- * "<value"
- * ">value"
- * "=value"
- * "pattern"
- **/
-GnmCriteria *
-parse_criteria (GnmValue const *crit_val, GODateConventions const *date_conv,
- gboolean anchor_end)
-{
- int len;
- char const *criteria;
- GnmCriteria *res = g_new0 (GnmCriteria, 1);
- GnmValue *empty;
-
- res->iter_flags = CELL_ITER_IGNORE_BLANK;
- res->date_conv = date_conv;
-
- if (VALUE_IS_NUMBER (crit_val)) {
- res->fun = criteria_test_equal;
- res->x = value_dup (crit_val);
- return res;
- }
-
- criteria = value_peek_string (crit_val);
- if (strncmp (criteria, "<=", 2) == 0) {
- res->fun = criteria_test_less_or_equal;
- len = 2;
- } else if (strncmp (criteria, ">=", 2) == 0) {
- res->fun = criteria_test_greater_or_equal;
- len = 2;
- } else if (strncmp (criteria, "<>", 2) == 0) {
- /* "<>" by itself is special: */
- res->fun = (criteria[2] == 0) ? criteria_test_nonempty : criteria_test_unequal;
- len = 2;
- } else if (*criteria == '<') {
- res->fun = criteria_test_less;
- len = 1;
- } else if (*criteria == '=') {
- /* "=" by itself is special: */
- res->fun = (criteria[1] == 0) ? criteria_test_empty : criteria_test_equal;
- len = 1;
- } else if (*criteria == '>') {
- res->fun = criteria_test_greater;
- len = 1;
- } else {
- res->fun = criteria_test_match;
- res->has_rx = (gnm_regcomp_XL (&res->rx, criteria, GO_REG_ICASE, TRUE, anchor_end) ==
GO_REG_OK);
- len = 0;
- }
-
- res->x = format_match_number (criteria + len, NULL, date_conv);
- if (res->x == NULL)
- res->x = value_new_string (criteria + len);
- else if (len == 0 && VALUE_IS_NUMBER (res->x))
- res->fun = criteria_test_equal;
-
- empty = value_new_empty ();
- if (res->fun (empty, res))
- res->iter_flags &= ~CELL_ITER_IGNORE_BLANK;
- value_release (empty);
- res->ref_count = 1;
-
- return res;
-}
-
-
-static GSList *
-parse_criteria_range (Sheet *sheet, int b_col, int b_row, int e_col, int e_row,
- int *field_ind, gboolean anchor_end)
-{
- GSList *criterias = NULL;
- GODateConventions const *date_conv =
- workbook_date_conv (sheet->workbook);
- int i, j;
-
- for (i = b_row; i <= e_row; i++) {
- GnmDBCriteria *new_criteria = g_new (GnmDBCriteria, 1);
- GSList *conditions = NULL;
-
- for (j = b_col; j <= e_col; j++) {
- GnmCriteria *cond;
- GnmCell *cell = sheet_cell_get (sheet, j, i);
- if (cell != NULL)
- gnm_cell_eval (cell);
- if (gnm_cell_is_empty (cell))
- continue;
-
- cond = parse_criteria (cell->value, date_conv,
- anchor_end);
- cond->column = (field_ind != NULL)
- ? field_ind[j - b_col]
- : j - b_col;
- conditions = g_slist_prepend (conditions, cond);
- }
-
- new_criteria->conditions = g_slist_reverse (conditions);
- criterias = g_slist_prepend (criterias, new_criteria);
- }
-
- return g_slist_reverse (criterias);
-}
-
-/**
- * parse_database_criteria:
- * @ep: #GnmEvalPos
- * @database: #GnmValue
- * @criteria: #GnmValue
- *
- * Parses the criteria cell range.
- * Returns: (element-type GnmDBCriteria) (transfer full):
- */
-GSList *
-parse_database_criteria (GnmEvalPos const *ep, GnmValue const *database, GnmValue const *criteria)
-{
- Sheet *sheet;
- GnmCell *cell;
- int i;
- int b_col, b_row, e_col, e_row;
- int *field_ind;
-
- g_return_val_if_fail (VALUE_IS_CELLRANGE (criteria), NULL);
-
- 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;
-
- if (e_col < b_col) {
- int tmp = b_col;
- b_col = e_col;
- e_col = tmp;
- }
-
- /* 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;
- gnm_cell_eval (cell);
- if (gnm_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,
- FALSE);
-}
-
-/**
- * find_rows_that_match:
- * @sheet: #Sheet
- * @first_col: first column.
- * @first_row: first row.
- * @last_col: last column.
- * @last_row: laset row.
- * @criterias: (element-type GnmDBCriteria): the criteria to use.
- * @unique_only:
- *
- * Finds the rows from the given database that match the criteria.
- * Returns: (element-type int) (transfer full): the list of matching rows.
- **/
-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 *rows = NULL;
- GSList const *crit_ptr, *cond_ptr;
- int row;
- gboolean add_flag;
- char const *t1, *t2;
- GnmCell *test_cell;
- GnmValue const *empty = value_new_empty ();
-
- for (row = first_row; row <= last_row; row++) {
- add_flag = TRUE;
- for (crit_ptr = criterias; crit_ptr; crit_ptr = crit_ptr->next) {
- GnmDBCriteria const *crit = crit_ptr->data;
- add_flag = TRUE;
- for (cond_ptr = crit->conditions;
- cond_ptr != NULL ; cond_ptr = cond_ptr->next) {
- GnmCriteria *cond = cond_ptr->data;
- test_cell = sheet_cell_get (sheet, cond->column, row);
- if (test_cell != NULL)
- gnm_cell_eval (test_cell);
- if (!cond->fun (test_cell ? test_cell->value : empty, cond)) {
- add_flag = FALSE;
- break;
- }
- }
-
- 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++) {
- test_cell = sheet_cell_get (sheet, i, trow);
- cell = sheet_cell_get (sheet, i, row);
-
- /* FIXME: this is probably not right, but crashing is more
wrong. */
- if (test_cell == NULL || cell == NULL)
- continue;
-
- 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);
-}
-
-/****************************************************************************/
-
GType
gnm_value_get_type (void)
{
diff --git a/src/value.h b/src/value.h
index 6d9ed08..e7582b9 100644
--- a/src/value.h
+++ b/src/value.h
@@ -191,39 +191,6 @@ extern GnmValueErr const value_terminate_err;
void value_array_set (GnmValue *array, int col, int row, GnmValue *v);
-typedef struct _GnmCriteria GnmCriteria;
-
-/* FIXME: this stuff below ought to go elsewhere. */
-typedef gboolean (*GnmCriteriaFunc) (GnmValue const *x, GnmCriteria *crit);
-struct _GnmCriteria {
- GnmCriteriaFunc fun;
- GnmValue *x;
- int column; /* absolute */
- CellIterFlags iter_flags;
- GODateConventions const *date_conv;
- GORegexp rx;
- gboolean has_rx;
- unsigned ref_count; /* for boxed type */
-};
-GType gnm_criteria_get_type (void);
-
-typedef struct {
- int row; /* absolute */
- GSList *conditions;
-} GnmDBCriteria;
-
-GnmCriteria *parse_criteria (GnmValue const *crit_val,
- GODateConventions const *date_conv,
- gboolean anchor_end);
-void free_criteria (GnmCriteria *criteria);
-void free_criterias (GSList *criterias);
-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 *parse_database_criteria (GnmEvalPos const *ep,
- GnmValue const *database, GnmValue const *criteria);
-int find_column_of_field (GnmEvalPos const *ep,
- GnmValue const *database, GnmValue const *field);
/* Protected */
void value_init (void);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]