2 * validation.c: Implementation of validation.
4 * Copyright (C) Jody Goldberg <jody@gnome.org>
7 * Almer S. Tigelaar <almer@gnome.org>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, see <https://www.gnu.org/licenses/>.
23 #include <gnumeric-config.h>
25 #include <validation.h>
26 #include <validation-combo.h>
35 #include <workbook-control.h>
36 #include <parse-util.h>
38 #include <sheet-view.h>
39 #include <sheet-object.h>
40 #include <sheet-style.h>
41 #include <widgets/gnm-validation-combo-view.h>
42 #include <widgets/gnm-cell-combo-view.h>
43 #include <gsf/gsf-impl-utils.h>
45 #include <glib/gi18n-lib.h>
48 gboolean errors_not_allowed
;
49 gboolean strings_not_allowed
;
50 gboolean bool_always_ok
;
52 { FALSE
, FALSE
, TRUE
}, /* ANY */
53 { TRUE
, TRUE
, TRUE
}, /* AS_INT */
54 { TRUE
, TRUE
, TRUE
}, /* AS_NUMBER */
55 { TRUE
, FALSE
, FALSE
}, /* IN_LIST */
56 { TRUE
, TRUE
, TRUE
}, /* AS_DATE */
57 { TRUE
, TRUE
, TRUE
}, /* AS_TIME */
58 { TRUE
, FALSE
, FALSE
}, /* TEXT_LENGTH */
59 { FALSE
, FALSE
, FALSE
} /* CUSTOM */
62 #define NONE (GnmExprOp)-1
70 /* Note: no entry for GNM_VALIDATION_OP_NONE */
71 { 2, { GNM_EXPR_OP_GTE
, GNM_EXPR_OP_LTE
}, 2, N_("Between") },
72 { 2, { GNM_EXPR_OP_LT
, GNM_EXPR_OP_GT
}, 1, N_("Not_Between") },
73 { 1, { GNM_EXPR_OP_EQUAL
, NONE
}, 1, N_("Equal") },
74 { 1, { GNM_EXPR_OP_NOT_EQUAL
, NONE
}, 1, N_("Not Equal") },
75 { 1, { GNM_EXPR_OP_GT
, NONE
}, 1, N_("Greater Than") },
76 { 1, { GNM_EXPR_OP_LT
, NONE
}, 1, N_("Less Than") },
77 { 1, { GNM_EXPR_OP_GTE
, NONE
}, 1, N_("Greater than or Equal") },
78 { 1, { GNM_EXPR_OP_LTE
, NONE
}, 1, N_("Less than or Equal") },
83 /***************************************************************************/
85 static GObjectClass
*gvc_parent_klass
;
88 gnm_validation_combo_finalize (GObject
*object
)
90 GnmValidationCombo
*vcombo
= GNM_VALIDATION_COMBO (object
);
92 if (NULL
!= vcombo
->validation
) {
93 gnm_validation_unref (vcombo
->validation
);
94 vcombo
->validation
= NULL
;
97 gvc_parent_klass
->finalize (object
);
101 gnm_validation_combo_init (G_GNUC_UNUSED SheetObject
*so
)
105 static SheetObjectView
*
106 gnm_validation_combo_view_new (SheetObject
*so
, SheetObjectViewContainer
*container
)
108 return gnm_cell_combo_view_new (so
,
109 gnm_validation_combo_view_get_type (), container
);
113 gnm_validation_combo_class_init (GObjectClass
*gobject_class
)
115 SheetObjectClass
*so_class
= GNM_SO_CLASS (gobject_class
);
116 gobject_class
->finalize
= gnm_validation_combo_finalize
;
117 so_class
->new_view
= gnm_validation_combo_view_new
;
119 gvc_parent_klass
= g_type_class_peek_parent (gobject_class
);
122 typedef SheetObjectClass GnmValidationComboClass
;
123 GSF_CLASS (GnmValidationCombo
, gnm_validation_combo
,
124 gnm_validation_combo_class_init
, gnm_validation_combo_init
,
125 gnm_cell_combo_get_type ())
128 gnm_validation_combo_new (GnmValidation
const *val
, SheetView
*sv
)
130 GnmValidationCombo
*vcombo
;
132 g_return_val_if_fail (val
!= NULL
, NULL
);
133 g_return_val_if_fail (sv
!= NULL
, NULL
);
135 vcombo
= g_object_new (GNM_VALIDATION_COMBO_TYPE
, "sheet-view", sv
, NULL
);
136 gnm_validation_ref (vcombo
->validation
= val
);
137 return GNM_SO (vcombo
);
140 /***************************************************************************/
143 gnm_validation_style_get_type (void)
145 static GType etype
= 0;
147 static GEnumValue
const values
[] = {
148 { GNM_VALIDATION_STYLE_NONE
,
149 "GNM_VALIDATION_STYLE_NONE", "none"},
150 { GNM_VALIDATION_STYLE_STOP
,
151 "GNM_VALIDATION_STYLE_STOP", "stop"},
152 { GNM_VALIDATION_STYLE_WARNING
,
153 "GNM_VALIDATION_STYLE_WARNING", "warning"},
154 { GNM_VALIDATION_STYLE_INFO
,
155 "GNM_VALIDATION_STYLE_INFO", "info"},
156 { GNM_VALIDATION_STYLE_PARSE_ERROR
,
157 "GNM_VALIDATION_STYLE_PARSE_ERROR", "parse-error"},
160 etype
= g_enum_register_static ("GnmValidationStyle",
167 gnm_validation_type_get_type (void)
169 static GType etype
= 0;
171 static GEnumValue
const values
[] = {
172 { GNM_VALIDATION_TYPE_ANY
,
173 "GNM_VALIDATION_TYPE_ANY", "any"},
174 { GNM_VALIDATION_TYPE_AS_INT
,
175 "GNM_VALIDATION_TYPE_AS_INT", "int"},
176 { GNM_VALIDATION_TYPE_AS_NUMBER
,
177 "GNM_VALIDATION_TYPE_AS_NUMBER", "number"},
178 { GNM_VALIDATION_TYPE_IN_LIST
,
179 "GNM_VALIDATION_TYPE_IN_LIST", "list"},
180 { GNM_VALIDATION_TYPE_AS_DATE
,
181 "GNM_VALIDATION_TYPE_AS_DATE", "date"},
182 { GNM_VALIDATION_TYPE_AS_TIME
,
183 "GNM_VALIDATION_TYPE_AS_TIME", "time"},
184 { GNM_VALIDATION_TYPE_TEXT_LENGTH
,
185 "GNM_VALIDATION_TYPE_TEXT_LENGTH", "length"},
186 { GNM_VALIDATION_TYPE_CUSTOM
,
187 "GNM_VALIDATION_TYPE_CUSTOM", "custom"},
190 etype
= g_enum_register_static ("GnmValidationType",
197 gnm_validation_op_get_type (void)
199 static GType etype
= 0;
201 static GEnumValue
const values
[] = {
202 { GNM_VALIDATION_OP_NONE
,
203 "GNM_VALIDATION_OP_NONE", "none"},
204 { GNM_VALIDATION_OP_BETWEEN
,
205 "GNM_VALIDATION_OP_BETWEEN", "between"},
206 { GNM_VALIDATION_OP_NOT_BETWEEN
,
207 "GNM_VALIDATION_OP_NOT_BETWEEN", "not-between"},
208 { GNM_VALIDATION_OP_EQUAL
,
209 "GNM_VALIDATION_OP_EQUAL", "equal"},
210 { GNM_VALIDATION_OP_NOT_EQUAL
,
211 "GNM_VALIDATION_OP_NOT_EQUAL", "not-equal"},
212 { GNM_VALIDATION_OP_GT
,
213 "GNM_VALIDATION_OP_GT", "gt"},
214 { GNM_VALIDATION_OP_LT
,
215 "GNM_VALIDATION_OP_LT", "lt"},
216 { GNM_VALIDATION_OP_GTE
,
217 "GNM_VALIDATION_OP_GTE", "gte"},
218 { GNM_VALIDATION_OP_LTE
,
219 "GNM_VALIDATION_OP_LTE", "lte"},
222 etype
= g_enum_register_static ("GnmValidationOp",
229 /***************************************************************************/
232 * gnm_validation_new:
233 * @style: #ValidationStyle
236 * @title: will be copied.
237 * @msg: will be copied.
238 * @texpr0: (transfer full) (nullable): first expression
239 * @texpr1: (transfer full) (nullable): second expression
243 * Does _NOT_ require all necessary information to be set here.
244 * gnm_validation_set_expr can be used to change the expressions after creation,
245 * and gnm_validation_is_ok can be used to ensure that things are properly
248 * Returns: (transfer full): a new @GnmValidation object
251 gnm_validation_new (ValidationStyle style
,
255 char const *title
, char const *msg
,
256 GnmExprTop
const *texpr0
, GnmExprTop
const *texpr1
,
257 gboolean allow_blank
, gboolean use_dropdown
)
262 g_return_val_if_fail ((size_t)type
< G_N_ELEMENTS (typeinfo
), NULL
);
263 g_return_val_if_fail (op
>= GNM_VALIDATION_OP_NONE
, NULL
);
264 g_return_val_if_fail (op
< (int)G_N_ELEMENTS (opinfo
), NULL
);
265 g_return_val_if_fail (IS_SHEET (sheet
), NULL
);
268 case GNM_VALIDATION_TYPE_CUSTOM
:
269 case GNM_VALIDATION_TYPE_IN_LIST
:
271 if (op
!= GNM_VALIDATION_OP_NONE
) {
273 * This can happen if an .xls file was saved
276 op
= GNM_VALIDATION_OP_NONE
;
279 case GNM_VALIDATION_TYPE_ANY
:
283 nops
= (op
== GNM_VALIDATION_OP_NONE
) ? 0 : opinfo
[op
].nops
;
286 v
= g_new0 (GnmValidation
, 1);
288 v
->title
= title
&& title
[0] ? go_string_new (title
) : NULL
;
289 v
->msg
= msg
&& msg
[0] ? go_string_new (msg
) : NULL
;
291 dependent_managed_init (&v
->deps
[0], sheet
);
294 dependent_managed_set_expr (&v
->deps
[0], texpr0
);
295 gnm_expr_top_unref (texpr0
);
298 dependent_managed_init (&v
->deps
[1], sheet
);
301 dependent_managed_set_expr (&v
->deps
[1], texpr1
);
302 gnm_expr_top_unref (texpr1
);
308 v
->allow_blank
= (allow_blank
!= FALSE
);
309 v
->use_dropdown
= (use_dropdown
!= FALSE
);
315 gnm_validation_dup (GnmValidation
*v
)
320 g_return_val_if_fail (v
!= NULL
, NULL
);
322 dst
= gnm_validation_new (v
->style
, v
->type
, v
->op
,
323 gnm_validation_get_sheet (v
),
324 v
->title
? v
->title
->str
: NULL
,
325 v
->msg
? v
->msg
->str
: NULL
,
327 v
->allow_blank
, v
->use_dropdown
);
328 for (i
= 0; i
< 2; i
++)
329 gnm_validation_set_expr (dst
, v
->deps
[i
].texpr
, i
);
334 gnm_validation_equal (GnmValidation
const *a
, GnmValidation
const *b
,
335 gboolean relax_sheet
)
339 g_return_val_if_fail (a
!= NULL
, FALSE
);
340 g_return_val_if_fail (b
!= NULL
, FALSE
);
346 gnm_validation_get_sheet (a
) != gnm_validation_get_sheet (b
))
349 if (!(g_strcmp0 (a
->title
? a
->title
->str
: NULL
,
350 b
->title
? b
->title
->str
: NULL
) == 0 &&
351 g_strcmp0 (a
->msg
? a
->msg
->str
: NULL
,
352 b
->msg
? b
->msg
->str
: NULL
) == 0 &&
353 a
->style
== b
->style
&&
354 a
->type
== b
->type
&&
356 a
->allow_blank
== b
->allow_blank
&&
357 a
->use_dropdown
== b
->use_dropdown
))
360 for (i
= 0; i
< 2; i
++)
361 if (!gnm_expr_top_equal (a
->deps
[i
].texpr
, b
->deps
[i
].texpr
))
369 gnm_validation_ref (GnmValidation
const *v
)
371 g_return_val_if_fail (v
!= NULL
, NULL
);
372 ((GnmValidation
*)v
)->ref_count
++;
373 return ((GnmValidation
*)v
);
377 gnm_validation_unref (GnmValidation
const *val
)
379 GnmValidation
*v
= (GnmValidation
*)val
;
381 g_return_if_fail (v
!= NULL
);
385 if (v
->ref_count
< 1) {
388 go_string_unref (v
->title
);
391 go_string_unref (v
->msg
);
394 for (i
= 0 ; i
< 2 ; i
++)
395 dependent_managed_set_expr (&v
->deps
[i
], NULL
);
401 gnm_validation_get_type (void)
406 t
= g_boxed_type_register_static ("GnmValidation",
407 (GBoxedCopyFunc
)gnm_validation_ref
,
408 (GBoxedFreeFunc
)gnm_validation_unref
);
414 * gnm_validation_get_sheet:
417 * Returns: (transfer none): the sheet.
420 gnm_validation_get_sheet (GnmValidation
const *v
)
422 g_return_val_if_fail (v
!= NULL
, NULL
);
423 return v
->deps
[0].sheet
;
427 gnm_validation_set_sheet (GnmValidation
*v
, Sheet
*sheet
)
431 g_return_if_fail (v
!= NULL
);
432 g_return_if_fail (IS_SHEET (sheet
));
434 for (i
= 0; i
< 2; i
++)
435 dependent_managed_set_sheet (&v
->deps
[i
], sheet
);
440 * gnm_validation_set_expr:
442 * @texpr: #GnmExprTop
445 * Assign an expression to a validation. gnm_validation_is_ok can be used to
446 * verify that @v has all of the required information.
449 gnm_validation_set_expr (GnmValidation
*v
,
450 GnmExprTop
const *texpr
, unsigned indx
)
452 g_return_if_fail (indx
<= 1);
454 dependent_managed_set_expr (&v
->deps
[indx
], texpr
);
458 gnm_validation_is_ok (GnmValidation
const *v
)
463 case GNM_VALIDATION_TYPE_CUSTOM
:
464 case GNM_VALIDATION_TYPE_IN_LIST
:
467 case GNM_VALIDATION_TYPE_ANY
:
470 default: nops
= (v
->op
== GNM_VALIDATION_OP_NONE
) ? 0 : opinfo
[v
->op
].nops
;
473 for (i
= 0 ; i
< 2 ; i
++)
474 if (v
->deps
[i
].texpr
== NULL
) {
476 return g_error_new (1, 0, N_("Missing formula for validation"));
479 return g_error_new (1, 0, N_("Extra formula for validation"));
485 static ValidationStatus
486 validation_barf (WorkbookControl
*wbc
, GnmValidation
const *gv
,
487 char *def_msg
, gboolean
*showed_dialog
)
489 char const *msg
= gv
->msg
? gv
->msg
->str
: def_msg
;
490 char const *title
= gv
->title
? gv
->title
->str
: _("Gnumeric: Validation");
491 ValidationStatus result
;
493 if (gv
->style
== GNM_VALIDATION_STYLE_NONE
) {
494 /* Invalid, but we're asked to ignore. */
495 result
= GNM_VALIDATION_STATUS_VALID
;
497 if (showed_dialog
) *showed_dialog
= TRUE
;
498 result
= wb_control_validation_msg (wbc
, gv
->style
, title
, msg
);
505 cb_validate_custom (GnmValueIter
const *v_iter
, GnmValue
const *target
)
507 if (value_compare (v_iter
->v
, target
, FALSE
) == IS_EQUAL
)
508 return VALUE_TERMINATE
;
515 return validation_barf (wbc, v, msg, showed_dialog); \
519 * gnm_validation_eval:
524 * @showed_dialog: (out) (optional):
526 * Checks the validation in @mstyle, if any. Set @showed_dialog to %TRUE
527 * if a dialog was showed as a result.
530 gnm_validation_eval (WorkbookControl
*wbc
, GnmStyle
const *mstyle
,
531 Sheet
*sheet
, GnmCellPos
const *pos
,
532 gboolean
*showed_dialog
)
534 GnmValidation
const *v
;
541 if (showed_dialog
) *showed_dialog
= FALSE
;
543 v
= gnm_style_get_validation (mstyle
);
545 return GNM_VALIDATION_STATUS_VALID
;
547 if (v
->type
== GNM_VALIDATION_TYPE_ANY
)
548 return GNM_VALIDATION_STATUS_VALID
;
550 cell
= sheet_cell_get (sheet
, pos
->col
, pos
->row
);
552 gnm_cell_eval (cell
);
554 if (gnm_cell_is_empty (cell
)) {
556 return GNM_VALIDATION_STATUS_VALID
;
557 BARF (g_strdup_printf (_("Cell %s is not permitted to be blank"),
562 switch (val
->v_any
.type
) {
564 if (typeinfo
[v
->type
].errors_not_allowed
)
565 BARF (g_strdup_printf (_("Cell %s is not permitted to contain error values"),
570 if (typeinfo
[v
->type
].bool_always_ok
)
571 return GNM_VALIDATION_STATUS_VALID
;
575 if (typeinfo
[v
->type
].strings_not_allowed
)
576 BARF (g_strdup_printf (_("Cell %s is not permitted to contain strings"),
584 eval_pos_init_cell (&ep
, cell
);
587 case GNM_VALIDATION_TYPE_AS_INT
:
588 x
= value_get_as_float (val
);
589 if (gnm_fake_floor (x
) == gnm_fake_ceil (x
))
592 BARF (g_strdup_printf (_("'%s' is not an integer"),
593 value_peek_string (val
)));
595 case GNM_VALIDATION_TYPE_AS_NUMBER
:
596 x
= value_get_as_float (val
);
599 case GNM_VALIDATION_TYPE_AS_DATE
: /* What the hell does this do? */
600 x
= value_get_as_float (val
);
602 BARF (g_strdup_printf (_("'%s' is not a valid date"),
603 value_peek_string (val
)));
607 case GNM_VALIDATION_TYPE_AS_TIME
: /* What the hell does this do? */
608 x
= value_get_as_float (val
);
611 case GNM_VALIDATION_TYPE_IN_LIST
: {
612 GnmExprTop
const *texpr
= v
->deps
[0].texpr
;
614 GnmValue
*list
= gnm_expr_top_eval
616 GNM_EXPR_EVAL_PERMIT_NON_SCALAR
| GNM_EXPR_EVAL_PERMIT_EMPTY
);
617 GnmValue
*res
= value_area_foreach (list
, &ep
, CELL_ITER_IGNORE_BLANK
,
618 (GnmValueIterFunc
) cb_validate_custom
, val
);
619 value_release (list
);
622 char *expr_str
= gnm_expr_top_as_string
624 parse_pos_init_evalpos (&pp
, &ep
),
626 char *msg
= g_strdup_printf (_("%s does not contain the new value."), expr_str
);
631 return GNM_VALIDATION_STATUS_VALID
;
634 case GNM_VALIDATION_TYPE_TEXT_LENGTH
:
635 /* XL appears to use a very basic value->string mapping that
636 * ignores formatting.
637 * eg len (12/13/01) == len (37238) = 5
638 * This seems wrong for
640 x
= g_utf8_strlen (value_peek_string (val
), -1);
643 case GNM_VALIDATION_TYPE_CUSTOM
: {
645 GnmExprTop
const *texpr
= v
->deps
[0].texpr
;
648 return GNM_VALIDATION_STATUS_VALID
;
650 val
= gnm_expr_top_eval (texpr
, &ep
, GNM_EXPR_EVAL_SCALAR_NON_EMPTY
);
651 valid
= value_get_as_bool (val
, NULL
);
655 return GNM_VALIDATION_STATUS_VALID
;
658 char *expr_str
= gnm_expr_top_as_string
660 parse_pos_init_evalpos (&pp
, &ep
),
662 char *msg
= g_strdup_printf (_("%s is not true."), expr_str
);
669 g_assert_not_reached ();
670 return GNM_VALIDATION_STATUS_VALID
;
673 if (v
->op
== GNM_VALIDATION_OP_NONE
)
674 return GNM_VALIDATION_STATUS_VALID
;
677 for (i
= 0; i
< opinfo
[v
->op
].nops
; i
++) {
678 GnmExprTop
const *texpr_i
= v
->deps
[i
].texpr
;
679 GnmExprTop
const *texpr
;
687 texpr
= gnm_expr_top_new
689 (gnm_expr_new_constant (value_new_float (x
)),
690 opinfo
[v
->op
].ops
[i
],
691 gnm_expr_copy (texpr_i
->expr
)));
692 cres
= gnm_expr_top_eval
693 (texpr
, &ep
, GNM_EXPR_EVAL_SCALAR_NON_EMPTY
);
694 if (value_get_as_bool (cres
, NULL
))
696 value_release (cres
);
697 gnm_expr_top_unref (texpr
);
700 if (nok
< opinfo
[v
->op
].ntrue
)
701 BARF (g_strdup_printf (_("%s is out of permitted range"),
702 value_peek_string (val
)));
704 return GNM_VALIDATION_STATUS_VALID
;
710 WorkbookControl
*wbc
;
712 GnmCellPos
const *pos
;
713 gboolean
*showed_dialog
;
714 ValidationStatus status
;
718 validation_eval_range_cb (GnmCellIter
const *iter
, validation_eval_t
*closure
)
720 ValidationStatus status
;
721 gboolean showed_dialog
;
722 GnmStyle
const *mstyle
= sheet_style_get
723 (closure
->sheet
, iter
->pp
.eval
.col
, iter
->pp
.eval
.row
);
725 if (mstyle
!= NULL
) {
726 status
= gnm_validation_eval (closure
->wbc
, mstyle
,
727 closure
->sheet
, &iter
->pp
.eval
,
729 if (closure
->showed_dialog
)
730 *closure
->showed_dialog
= *closure
->showed_dialog
|| showed_dialog
;
732 if (status
!= GNM_VALIDATION_STATUS_VALID
) {
733 closure
->status
= status
;
734 return VALUE_TERMINATE
;
742 gnm_validation_eval_range (WorkbookControl
*wbc
,
743 Sheet
*sheet
, GnmCellPos
const *pos
,
745 gboolean
*showed_dialog
)
748 validation_eval_t closure
;
750 GnmValue
*cell_range
= value_new_cellrange_r (sheet
, r
);
753 closure
.sheet
= sheet
;
755 closure
.showed_dialog
= showed_dialog
;
756 closure
.status
= GNM_VALIDATION_STATUS_VALID
;
758 eval_pos_init_pos (&ep
, sheet
, pos
);
760 result
= workbook_foreach_cell_in_range (&ep
, cell_range
, CELL_ITER_ALL
,
761 (CellIterFunc
) validation_eval_range_cb
,
764 value_release (cell_range
);
767 return GNM_VALIDATION_STATUS_VALID
;
768 return closure
.status
;