1 /* vim: set sw=8: -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
3 * validation.c: Implementation of validation.
5 * Copyright (C) Jody Goldberg <jody@gnome.org>
8 * Almer S. Tigelaar <almer@gnome.org>
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, see <https://www.gnu.org/licenses/>.
24 #include <gnumeric-config.h>
26 #include "validation.h"
27 #include "validation-combo.h"
36 #include "workbook-control.h"
37 #include "parse-util.h"
39 #include "sheet-view.h"
40 #include "sheet-object.h"
41 #include "sheet-style.h"
42 #include "gnm-validation-combo-view.h"
43 #include "gnm-cell-combo-view.h"
44 #include <gsf/gsf-impl-utils.h>
46 #include <glib/gi18n-lib.h>
49 gboolean errors_not_allowed
;
50 gboolean strings_not_allowed
;
51 gboolean bool_always_ok
;
53 { FALSE
, FALSE
, TRUE
}, /* ANY */
54 { TRUE
, TRUE
, TRUE
}, /* AS_INT */
55 { TRUE
, TRUE
, TRUE
}, /* AS_NUMBER */
56 { TRUE
, FALSE
, FALSE
}, /* IN_LIST */
57 { TRUE
, TRUE
, TRUE
}, /* AS_DATE */
58 { TRUE
, TRUE
, TRUE
}, /* AS_TIME */
59 { TRUE
, FALSE
, FALSE
}, /* TEXT_LENGTH */
60 { FALSE
, FALSE
, FALSE
} /* CUSTOM */
63 #define NONE (GnmExprOp)-1
71 /* Note: no entry for GNM_VALIDATION_OP_NONE */
72 { 2, { GNM_EXPR_OP_GTE
, GNM_EXPR_OP_LTE
}, 2, N_("Between") },
73 { 2, { GNM_EXPR_OP_LT
, GNM_EXPR_OP_GT
}, 1, N_("Not_Between") },
74 { 1, { GNM_EXPR_OP_EQUAL
, NONE
}, 1, N_("Equal") },
75 { 1, { GNM_EXPR_OP_NOT_EQUAL
, NONE
}, 1, N_("Not Equal") },
76 { 1, { GNM_EXPR_OP_GT
, NONE
}, 1, N_("Greater Than") },
77 { 1, { GNM_EXPR_OP_LT
, NONE
}, 1, N_("Less Than") },
78 { 1, { GNM_EXPR_OP_GTE
, NONE
}, 1, N_("Greater than or Equal") },
79 { 1, { GNM_EXPR_OP_LTE
, NONE
}, 1, N_("Less than or Equal") },
84 /***************************************************************************/
86 static GObjectClass
*gvc_parent_klass
;
89 gnm_validation_combo_finalize (GObject
*object
)
91 GnmValidationCombo
*vcombo
= GNM_VALIDATION_COMBO (object
);
93 if (NULL
!= vcombo
->validation
) {
94 gnm_validation_unref (vcombo
->validation
);
95 vcombo
->validation
= NULL
;
98 gvc_parent_klass
->finalize (object
);
102 gnm_validation_combo_init (G_GNUC_UNUSED SheetObject
*so
)
106 static SheetObjectView
*
107 gnm_validation_combo_view_new (SheetObject
*so
, SheetObjectViewContainer
*container
)
109 return gnm_cell_combo_view_new (so
,
110 gnm_validation_combo_view_get_type (), container
);
114 gnm_validation_combo_class_init (GObjectClass
*gobject_class
)
116 SheetObjectClass
*so_class
= GNM_SO_CLASS (gobject_class
);
117 gobject_class
->finalize
= gnm_validation_combo_finalize
;
118 so_class
->new_view
= gnm_validation_combo_view_new
;
120 gvc_parent_klass
= g_type_class_peek_parent (gobject_class
);
123 typedef SheetObjectClass GnmValidationComboClass
;
124 GSF_CLASS (GnmValidationCombo
, gnm_validation_combo
,
125 gnm_validation_combo_class_init
, gnm_validation_combo_init
,
126 gnm_cell_combo_get_type ())
129 gnm_validation_combo_new (GnmValidation
const *val
, SheetView
*sv
)
131 GnmValidationCombo
*vcombo
;
133 g_return_val_if_fail (val
!= NULL
, NULL
);
134 g_return_val_if_fail (sv
!= NULL
, NULL
);
136 vcombo
= g_object_new (GNM_VALIDATION_COMBO_TYPE
, "sheet-view", sv
, NULL
);
137 gnm_validation_ref (vcombo
->validation
= val
);
138 return GNM_SO (vcombo
);
141 /***************************************************************************/
144 gnm_validation_style_get_type (void)
146 static GType etype
= 0;
148 static GEnumValue
const values
[] = {
149 { GNM_VALIDATION_STYLE_NONE
,
150 "GNM_VALIDATION_STYLE_NONE", "none"},
151 { GNM_VALIDATION_STYLE_STOP
,
152 "GNM_VALIDATION_STYLE_STOP", "stop"},
153 { GNM_VALIDATION_STYLE_WARNING
,
154 "GNM_VALIDATION_STYLE_WARNING", "warning"},
155 { GNM_VALIDATION_STYLE_INFO
,
156 "GNM_VALIDATION_STYLE_INFO", "info"},
157 { GNM_VALIDATION_STYLE_PARSE_ERROR
,
158 "GNM_VALIDATION_STYLE_PARSE_ERROR", "parse-error"},
161 etype
= g_enum_register_static ("GnmValidationStyle",
168 gnm_validation_type_get_type (void)
170 static GType etype
= 0;
172 static GEnumValue
const values
[] = {
173 { GNM_VALIDATION_TYPE_ANY
,
174 "GNM_VALIDATION_TYPE_ANY", "any"},
175 { GNM_VALIDATION_TYPE_AS_INT
,
176 "GNM_VALIDATION_TYPE_AS_INT", "int"},
177 { GNM_VALIDATION_TYPE_AS_NUMBER
,
178 "GNM_VALIDATION_TYPE_AS_NUMBER", "number"},
179 { GNM_VALIDATION_TYPE_IN_LIST
,
180 "GNM_VALIDATION_TYPE_IN_LIST", "list"},
181 { GNM_VALIDATION_TYPE_AS_DATE
,
182 "GNM_VALIDATION_TYPE_AS_DATE", "date"},
183 { GNM_VALIDATION_TYPE_AS_TIME
,
184 "GNM_VALIDATION_TYPE_AS_TIME", "time"},
185 { GNM_VALIDATION_TYPE_TEXT_LENGTH
,
186 "GNM_VALIDATION_TYPE_TEXT_LENGTH", "length"},
187 { GNM_VALIDATION_TYPE_CUSTOM
,
188 "GNM_VALIDATION_TYPE_CUSTOM", "custom"},
191 etype
= g_enum_register_static ("GnmValidationType",
198 gnm_validation_op_get_type (void)
200 static GType etype
= 0;
202 static GEnumValue
const values
[] = {
203 { GNM_VALIDATION_OP_NONE
,
204 "GNM_VALIDATION_OP_NONE", "none"},
205 { GNM_VALIDATION_OP_BETWEEN
,
206 "GNM_VALIDATION_OP_BETWEEN", "between"},
207 { GNM_VALIDATION_OP_NOT_BETWEEN
,
208 "GNM_VALIDATION_OP_NOT_BETWEEN", "not-between"},
209 { GNM_VALIDATION_OP_EQUAL
,
210 "GNM_VALIDATION_OP_EQUAL", "equal"},
211 { GNM_VALIDATION_OP_NOT_EQUAL
,
212 "GNM_VALIDATION_OP_NOT_EQUAL", "not-equal"},
213 { GNM_VALIDATION_OP_GT
,
214 "GNM_VALIDATION_OP_GT", "gt"},
215 { GNM_VALIDATION_OP_LT
,
216 "GNM_VALIDATION_OP_LT", "lt"},
217 { GNM_VALIDATION_OP_GTE
,
218 "GNM_VALIDATION_OP_GTE", "gte"},
219 { GNM_VALIDATION_OP_LTE
,
220 "GNM_VALIDATION_OP_LTE", "lte"},
223 etype
= g_enum_register_static ("GnmValidationOp",
230 /***************************************************************************/
233 * gnm_validation_new :
234 * @title: will be copied.
235 * @msg: will be copied.
236 * @texpr0: absorb the reference to the expression (optionally %NULL).
237 * @texpr1: absorb the reference to the expression (optionally %NULL).
239 * Does _NOT_ require all necessary information to be set here.
240 * gnm_validation_set_expr can be used to change the expressions after creation,
241 * and gnm_validation_is_ok can be used to ensure that things are properly setup.
243 * Returns a new @GnmValidation object that needs to be unrefed.
246 gnm_validation_new (ValidationStyle style
,
250 char const *title
, char const *msg
,
251 GnmExprTop
const *texpr0
, GnmExprTop
const *texpr1
,
252 gboolean allow_blank
, gboolean use_dropdown
)
257 g_return_val_if_fail ((size_t)type
< G_N_ELEMENTS (typeinfo
), NULL
);
258 g_return_val_if_fail (op
>= GNM_VALIDATION_OP_NONE
, NULL
);
259 g_return_val_if_fail (op
< (int)G_N_ELEMENTS (opinfo
), NULL
);
260 g_return_val_if_fail (IS_SHEET (sheet
), NULL
);
263 case GNM_VALIDATION_TYPE_CUSTOM
:
264 case GNM_VALIDATION_TYPE_IN_LIST
:
266 if (op
!= GNM_VALIDATION_OP_NONE
) {
268 * This can happen if an .xls file was saved
271 op
= GNM_VALIDATION_OP_NONE
;
274 case GNM_VALIDATION_TYPE_ANY
:
278 nops
= (op
== GNM_VALIDATION_OP_NONE
) ? 0 : opinfo
[op
].nops
;
281 v
= g_new0 (GnmValidation
, 1);
283 v
->title
= title
&& title
[0] ? go_string_new (title
) : NULL
;
284 v
->msg
= msg
&& msg
[0] ? go_string_new (msg
) : NULL
;
286 dependent_managed_init (&v
->deps
[0], sheet
);
289 dependent_managed_set_expr (&v
->deps
[0], texpr0
);
290 gnm_expr_top_unref (texpr0
);
293 dependent_managed_init (&v
->deps
[1], sheet
);
296 dependent_managed_set_expr (&v
->deps
[1], texpr1
);
297 gnm_expr_top_unref (texpr1
);
303 v
->allow_blank
= (allow_blank
!= FALSE
);
304 v
->use_dropdown
= (use_dropdown
!= FALSE
);
310 gnm_validation_dup (GnmValidation
*v
)
315 g_return_val_if_fail (v
!= NULL
, NULL
);
317 dst
= gnm_validation_new (v
->style
, v
->type
, v
->op
,
318 gnm_validation_get_sheet (v
),
319 v
->title
? v
->title
->str
: NULL
,
320 v
->msg
? v
->msg
->str
: NULL
,
322 v
->allow_blank
, v
->use_dropdown
);
323 for (i
= 0; i
< 2; i
++)
324 gnm_validation_set_expr (dst
, v
->deps
[i
].texpr
, i
);
329 gnm_validation_equal (GnmValidation
const *a
, GnmValidation
const *b
,
330 gboolean relax_sheet
)
334 g_return_val_if_fail (a
!= NULL
, FALSE
);
335 g_return_val_if_fail (b
!= NULL
, FALSE
);
341 gnm_validation_get_sheet (a
) != gnm_validation_get_sheet (b
))
344 if (!(g_strcmp0 (a
->title
? a
->title
->str
: NULL
,
345 b
->title
? b
->title
->str
: NULL
) == 0 &&
346 g_strcmp0 (a
->msg
? a
->msg
->str
: NULL
,
347 b
->msg
? b
->msg
->str
: NULL
) == 0 &&
348 a
->style
== b
->style
&&
349 a
->type
== b
->type
&&
351 a
->allow_blank
== b
->allow_blank
&&
352 a
->use_dropdown
== b
->use_dropdown
))
355 for (i
= 0; i
< 2; i
++)
356 if (!gnm_expr_top_equal (a
->deps
[i
].texpr
, b
->deps
[i
].texpr
))
364 gnm_validation_ref (GnmValidation
const *v
)
366 g_return_val_if_fail (v
!= NULL
, NULL
);
367 ((GnmValidation
*)v
)->ref_count
++;
368 return ((GnmValidation
*)v
);
372 gnm_validation_unref (GnmValidation
const *val
)
374 GnmValidation
*v
= (GnmValidation
*)val
;
376 g_return_if_fail (v
!= NULL
);
380 if (v
->ref_count
< 1) {
383 if (v
->title
!= NULL
) {
384 go_string_unref (v
->title
);
387 if (v
->msg
!= NULL
) {
388 go_string_unref (v
->msg
);
391 for (i
= 0 ; i
< 2 ; i
++)
392 dependent_managed_set_expr (&v
->deps
[i
], NULL
);
398 gnm_validation_get_type (void)
403 t
= g_boxed_type_register_static ("GnmValidation",
404 (GBoxedCopyFunc
)gnm_validation_ref
,
405 (GBoxedFreeFunc
)gnm_validation_unref
);
411 * gnm_validation_get_sheet:
414 * Returns: (transfer none): the sheet.
417 gnm_validation_get_sheet (GnmValidation
const *v
)
419 g_return_val_if_fail (v
!= NULL
, NULL
);
420 return v
->deps
[0].sheet
;
424 gnm_validation_set_sheet (GnmValidation
*v
, Sheet
*sheet
)
428 g_return_if_fail (v
!= NULL
);
429 g_return_if_fail (IS_SHEET (sheet
));
431 for (i
= 0; i
< 2; i
++)
432 dependent_managed_set_sheet (&v
->deps
[i
], sheet
);
437 * gnm_validation_set_expr :
439 * @texpr: #GnmExprTop
442 * Assign an expression to a validation. gnm_validation_is_ok can be used to
443 * verify that @v has all of the required information.
446 gnm_validation_set_expr (GnmValidation
*v
,
447 GnmExprTop
const *texpr
, unsigned indx
)
449 g_return_if_fail (indx
<= 1);
451 dependent_managed_set_expr (&v
->deps
[indx
], texpr
);
455 gnm_validation_is_ok (GnmValidation
const *v
)
460 case GNM_VALIDATION_TYPE_CUSTOM
:
461 case GNM_VALIDATION_TYPE_IN_LIST
:
464 case GNM_VALIDATION_TYPE_ANY
:
467 default: nops
= (v
->op
== GNM_VALIDATION_OP_NONE
) ? 0 : opinfo
[v
->op
].nops
;
470 for (i
= 0 ; i
< 2 ; i
++)
471 if (v
->deps
[i
].texpr
== NULL
) {
473 return g_error_new (1, 0, N_("Missing formula for validation"));
476 return g_error_new (1, 0, N_("Extra formula for validation"));
482 static ValidationStatus
483 validation_barf (WorkbookControl
*wbc
, GnmValidation
const *gv
,
484 char *def_msg
, gboolean
*showed_dialog
)
486 char const *msg
= gv
->msg
? gv
->msg
->str
: def_msg
;
487 char const *title
= gv
->title
? gv
->title
->str
: _("Gnumeric: Validation");
488 ValidationStatus result
;
490 if (gv
->style
== GNM_VALIDATION_STYLE_NONE
) {
491 /* Invalid, but we're asked to ignore. */
492 result
= GNM_VALIDATION_STATUS_VALID
;
494 if (showed_dialog
) *showed_dialog
= TRUE
;
495 result
= wb_control_validation_msg (wbc
, gv
->style
, title
, msg
);
502 cb_validate_custom (GnmValueIter
const *v_iter
, GnmValue
const *target
)
504 if (value_compare (v_iter
->v
, target
, FALSE
) == IS_EQUAL
)
505 return VALUE_TERMINATE
;
512 return validation_barf (wbc, v, msg, showed_dialog); \
516 * gnm_validation_eval:
521 * validation set in the GnmStyle if applicable.
524 gnm_validation_eval (WorkbookControl
*wbc
, GnmStyle
const *mstyle
,
525 Sheet
*sheet
, GnmCellPos
const *pos
, gboolean
*showed_dialog
)
527 GnmValidation
const *v
;
534 if (showed_dialog
) *showed_dialog
= FALSE
;
536 v
= gnm_style_get_validation (mstyle
);
538 return GNM_VALIDATION_STATUS_VALID
;
540 if (v
->type
== GNM_VALIDATION_TYPE_ANY
)
541 return GNM_VALIDATION_STATUS_VALID
;
543 cell
= sheet_cell_get (sheet
, pos
->col
, pos
->row
);
545 gnm_cell_eval (cell
);
547 if (gnm_cell_is_empty (cell
)) {
549 return GNM_VALIDATION_STATUS_VALID
;
550 BARF (g_strdup_printf (_("Cell %s is not permitted to be blank"),
555 switch (val
->v_any
.type
) {
557 if (typeinfo
[v
->type
].errors_not_allowed
)
558 BARF (g_strdup_printf (_("Cell %s is not permitted to contain error values"),
563 if (typeinfo
[v
->type
].bool_always_ok
)
564 return GNM_VALIDATION_STATUS_VALID
;
568 if (typeinfo
[v
->type
].strings_not_allowed
)
569 BARF (g_strdup_printf (_("Cell %s is not permitted to contain strings"),
577 eval_pos_init_cell (&ep
, cell
);
580 case GNM_VALIDATION_TYPE_AS_INT
:
581 x
= value_get_as_float (val
);
582 if (gnm_fake_floor (x
) == gnm_fake_ceil (x
))
585 BARF (g_strdup_printf (_("'%s' is not an integer"),
586 value_peek_string (val
)));
588 case GNM_VALIDATION_TYPE_AS_NUMBER
:
589 x
= value_get_as_float (val
);
592 case GNM_VALIDATION_TYPE_AS_DATE
: /* What the hell does this do? */
593 x
= value_get_as_float (val
);
595 BARF (g_strdup_printf (_("'%s' is not a valid date"),
596 value_peek_string (val
)));
600 case GNM_VALIDATION_TYPE_AS_TIME
: /* What the hell does this do? */
601 x
= value_get_as_float (val
);
604 case GNM_VALIDATION_TYPE_IN_LIST
: {
605 GnmExprTop
const *texpr
= v
->deps
[0].texpr
;
607 GnmValue
*list
= gnm_expr_top_eval
609 GNM_EXPR_EVAL_PERMIT_NON_SCALAR
| GNM_EXPR_EVAL_PERMIT_EMPTY
);
610 GnmValue
*res
= value_area_foreach (list
, &ep
, CELL_ITER_IGNORE_BLANK
,
611 (GnmValueIterFunc
) cb_validate_custom
, val
);
612 value_release (list
);
615 char *expr_str
= gnm_expr_top_as_string
617 parse_pos_init_evalpos (&pp
, &ep
),
619 char *msg
= g_strdup_printf (_("%s does not contain the new value."), expr_str
);
624 return GNM_VALIDATION_STATUS_VALID
;
627 case GNM_VALIDATION_TYPE_TEXT_LENGTH
:
628 /* XL appears to use a very basic value->string mapping that
629 * ignores formatting.
630 * eg len (12/13/01) == len (37238) = 5
631 * This seems wrong for
633 x
= g_utf8_strlen (value_peek_string (val
), -1);
636 case GNM_VALIDATION_TYPE_CUSTOM
: {
638 GnmExprTop
const *texpr
= v
->deps
[0].texpr
;
641 return GNM_VALIDATION_STATUS_VALID
;
643 val
= gnm_expr_top_eval (texpr
, &ep
, GNM_EXPR_EVAL_SCALAR_NON_EMPTY
);
644 valid
= value_get_as_bool (val
, NULL
);
648 return GNM_VALIDATION_STATUS_VALID
;
651 char *expr_str
= gnm_expr_top_as_string
653 parse_pos_init_evalpos (&pp
, &ep
),
655 char *msg
= g_strdup_printf (_("%s is not true."), expr_str
);
662 g_assert_not_reached ();
663 return GNM_VALIDATION_STATUS_VALID
;
666 if (v
->op
== GNM_VALIDATION_OP_NONE
)
667 return GNM_VALIDATION_STATUS_VALID
;
670 for (i
= 0; i
< opinfo
[v
->op
].nops
; i
++) {
671 GnmExprTop
const *texpr_i
= v
->deps
[i
].texpr
;
672 GnmExprTop
const *texpr
;
680 texpr
= gnm_expr_top_new
682 (gnm_expr_new_constant (value_new_float (x
)),
683 opinfo
[v
->op
].ops
[i
],
684 gnm_expr_copy (texpr_i
->expr
)));
685 cres
= gnm_expr_top_eval
686 (texpr
, &ep
, GNM_EXPR_EVAL_SCALAR_NON_EMPTY
);
687 if (value_get_as_bool (cres
, NULL
))
689 value_release (cres
);
690 gnm_expr_top_unref (texpr
);
693 if (nok
< opinfo
[v
->op
].ntrue
)
694 BARF (g_strdup_printf (_("%s is out of permitted range"),
695 value_peek_string (val
)));
697 return GNM_VALIDATION_STATUS_VALID
;
703 WorkbookControl
*wbc
;
705 GnmCellPos
const *pos
;
706 gboolean
*showed_dialog
;
707 ValidationStatus status
;
711 validation_eval_range_cb (GnmCellIter
const *iter
, validation_eval_t
*closure
)
713 ValidationStatus status
;
714 gboolean showed_dialog
;
715 GnmStyle
const *mstyle
= sheet_style_get
716 (closure
->sheet
, iter
->pp
.eval
.col
, iter
->pp
.eval
.row
);
718 if (mstyle
!= NULL
) {
719 status
= gnm_validation_eval (closure
->wbc
, mstyle
,
720 closure
->sheet
, &iter
->pp
.eval
,
722 if (closure
->showed_dialog
)
723 *closure
->showed_dialog
= *closure
->showed_dialog
|| showed_dialog
;
725 if (status
!= GNM_VALIDATION_STATUS_VALID
) {
726 closure
->status
= status
;
727 return VALUE_TERMINATE
;
735 gnm_validation_eval_range (WorkbookControl
*wbc
,
736 Sheet
*sheet
, GnmCellPos
const *pos
, GnmRange
const *r
,
737 gboolean
*showed_dialog
)
740 validation_eval_t closure
;
742 GnmValue
*cell_range
= value_new_cellrange_r (sheet
, r
);
745 closure
.sheet
= sheet
;
747 closure
.showed_dialog
= showed_dialog
;
748 closure
.status
= GNM_VALIDATION_STATUS_VALID
;
750 eval_pos_init_pos (&ep
, sheet
, pos
);
752 result
= workbook_foreach_cell_in_range (&ep
, cell_range
, CELL_ITER_ALL
,
753 (CellIterFunc
) validation_eval_range_cb
,
756 value_release (cell_range
);
759 return GNM_VALIDATION_STATUS_VALID
;
760 return closure
.status
;