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_if_fail (v
!= NULL
);
367 ((GnmValidation
*)v
)->ref_count
++;
371 gnm_validation_unref (GnmValidation
const *val
)
373 GnmValidation
*v
= (GnmValidation
*)val
;
375 g_return_if_fail (v
!= NULL
);
379 if (v
->ref_count
< 1) {
382 if (v
->title
!= NULL
) {
383 go_string_unref (v
->title
);
386 if (v
->msg
!= NULL
) {
387 go_string_unref (v
->msg
);
390 for (i
= 0 ; i
< 2 ; i
++)
391 dependent_managed_set_expr (&v
->deps
[i
], NULL
);
397 gnm_validation_get_type (void)
402 t
= g_boxed_type_register_static ("GnmValidation",
403 (GBoxedCopyFunc
)gnm_validation_ref
,
404 (GBoxedFreeFunc
)gnm_validation_unref
);
410 * gnm_validation_get_sheet:
413 * Returns: (transfer none): the sheet.
416 gnm_validation_get_sheet (GnmValidation
const *v
)
418 g_return_val_if_fail (v
!= NULL
, NULL
);
419 return v
->deps
[0].sheet
;
423 gnm_validation_set_sheet (GnmValidation
*v
, Sheet
*sheet
)
427 g_return_if_fail (v
!= NULL
);
428 g_return_if_fail (IS_SHEET (sheet
));
430 for (i
= 0; i
< 2; i
++)
431 dependent_managed_set_sheet (&v
->deps
[i
], sheet
);
436 * gnm_validation_set_expr :
438 * @texpr: #GnmExprTop
441 * Assign an expression to a validation. gnm_validation_is_ok can be used to
442 * verify that @v has all of the required information.
445 gnm_validation_set_expr (GnmValidation
*v
,
446 GnmExprTop
const *texpr
, unsigned indx
)
448 g_return_if_fail (indx
<= 1);
450 dependent_managed_set_expr (&v
->deps
[indx
], texpr
);
454 gnm_validation_is_ok (GnmValidation
const *v
)
459 case GNM_VALIDATION_TYPE_CUSTOM
:
460 case GNM_VALIDATION_TYPE_IN_LIST
:
463 case GNM_VALIDATION_TYPE_ANY
:
466 default: nops
= (v
->op
== GNM_VALIDATION_OP_NONE
) ? 0 : opinfo
[v
->op
].nops
;
469 for (i
= 0 ; i
< 2 ; i
++)
470 if (v
->deps
[i
].texpr
== NULL
) {
472 return g_error_new (1, 0, N_("Missing formula for validation"));
475 return g_error_new (1, 0, N_("Extra formula for validation"));
481 static ValidationStatus
482 validation_barf (WorkbookControl
*wbc
, GnmValidation
const *gv
,
483 char *def_msg
, gboolean
*showed_dialog
)
485 char const *msg
= gv
->msg
? gv
->msg
->str
: def_msg
;
486 char const *title
= gv
->title
? gv
->title
->str
: _("Gnumeric: Validation");
487 ValidationStatus result
;
489 if (gv
->style
== GNM_VALIDATION_STYLE_NONE
) {
490 /* Invalid, but we're asked to ignore. */
491 result
= GNM_VALIDATION_STATUS_VALID
;
493 if (showed_dialog
) *showed_dialog
= TRUE
;
494 result
= wb_control_validation_msg (wbc
, gv
->style
, title
, msg
);
501 cb_validate_custom (GnmValueIter
const *v_iter
, GnmValue
const *target
)
503 if (value_compare (v_iter
->v
, target
, FALSE
) == IS_EQUAL
)
504 return VALUE_TERMINATE
;
511 return validation_barf (wbc, v, msg, showed_dialog); \
515 * gnm_validation_eval:
520 * validation set in the GnmStyle if applicable.
523 gnm_validation_eval (WorkbookControl
*wbc
, GnmStyle
const *mstyle
,
524 Sheet
*sheet
, GnmCellPos
const *pos
, gboolean
*showed_dialog
)
526 GnmValidation
const *v
;
533 if (showed_dialog
) *showed_dialog
= FALSE
;
535 v
= gnm_style_get_validation (mstyle
);
537 return GNM_VALIDATION_STATUS_VALID
;
539 if (v
->type
== GNM_VALIDATION_TYPE_ANY
)
540 return GNM_VALIDATION_STATUS_VALID
;
542 cell
= sheet_cell_get (sheet
, pos
->col
, pos
->row
);
544 gnm_cell_eval (cell
);
546 if (gnm_cell_is_empty (cell
)) {
548 return GNM_VALIDATION_STATUS_VALID
;
549 BARF (g_strdup_printf (_("Cell %s is not permitted to be blank"),
554 switch (val
->v_any
.type
) {
556 if (typeinfo
[v
->type
].errors_not_allowed
)
557 BARF (g_strdup_printf (_("Cell %s is not permitted to contain error values"),
562 if (typeinfo
[v
->type
].bool_always_ok
)
563 return GNM_VALIDATION_STATUS_VALID
;
567 if (typeinfo
[v
->type
].strings_not_allowed
)
568 BARF (g_strdup_printf (_("Cell %s is not permitted to contain strings"),
576 eval_pos_init_cell (&ep
, cell
);
579 case GNM_VALIDATION_TYPE_AS_INT
:
580 x
= value_get_as_float (val
);
581 if (gnm_fake_floor (x
) == gnm_fake_ceil (x
))
584 BARF (g_strdup_printf (_("'%s' is not an integer"),
585 value_peek_string (val
)));
587 case GNM_VALIDATION_TYPE_AS_NUMBER
:
588 x
= value_get_as_float (val
);
591 case GNM_VALIDATION_TYPE_AS_DATE
: /* What the hell does this do? */
592 x
= value_get_as_float (val
);
594 BARF (g_strdup_printf (_("'%s' is not a valid date"),
595 value_peek_string (val
)));
599 case GNM_VALIDATION_TYPE_AS_TIME
: /* What the hell does this do? */
600 x
= value_get_as_float (val
);
603 case GNM_VALIDATION_TYPE_IN_LIST
: {
604 GnmExprTop
const *texpr
= v
->deps
[0].texpr
;
606 GnmValue
*list
= gnm_expr_top_eval
608 GNM_EXPR_EVAL_PERMIT_NON_SCALAR
| GNM_EXPR_EVAL_PERMIT_EMPTY
);
609 GnmValue
*res
= value_area_foreach (list
, &ep
, CELL_ITER_IGNORE_BLANK
,
610 (GnmValueIterFunc
) cb_validate_custom
, val
);
611 value_release (list
);
614 char *expr_str
= gnm_expr_top_as_string
616 parse_pos_init_evalpos (&pp
, &ep
),
618 char *msg
= g_strdup_printf (_("%s does not contain the new value."), expr_str
);
623 return GNM_VALIDATION_STATUS_VALID
;
626 case GNM_VALIDATION_TYPE_TEXT_LENGTH
:
627 /* XL appears to use a very basic value->string mapping that
628 * ignores formatting.
629 * eg len (12/13/01) == len (37238) = 5
630 * This seems wrong for
632 x
= g_utf8_strlen (value_peek_string (val
), -1);
635 case GNM_VALIDATION_TYPE_CUSTOM
: {
637 GnmExprTop
const *texpr
= v
->deps
[0].texpr
;
640 return GNM_VALIDATION_STATUS_VALID
;
642 val
= gnm_expr_top_eval (texpr
, &ep
, GNM_EXPR_EVAL_SCALAR_NON_EMPTY
);
643 valid
= value_get_as_bool (val
, NULL
);
647 return GNM_VALIDATION_STATUS_VALID
;
650 char *expr_str
= gnm_expr_top_as_string
652 parse_pos_init_evalpos (&pp
, &ep
),
654 char *msg
= g_strdup_printf (_("%s is not true."), expr_str
);
661 g_assert_not_reached ();
662 return GNM_VALIDATION_STATUS_VALID
;
665 if (v
->op
== GNM_VALIDATION_OP_NONE
)
666 return GNM_VALIDATION_STATUS_VALID
;
669 for (i
= 0; i
< opinfo
[v
->op
].nops
; i
++) {
670 GnmExprTop
const *texpr_i
= v
->deps
[i
].texpr
;
671 GnmExprTop
const *texpr
;
679 texpr
= gnm_expr_top_new
681 (gnm_expr_new_constant (value_new_float (x
)),
682 opinfo
[v
->op
].ops
[i
],
683 gnm_expr_copy (texpr_i
->expr
)));
684 cres
= gnm_expr_top_eval
685 (texpr
, &ep
, GNM_EXPR_EVAL_SCALAR_NON_EMPTY
);
686 if (value_get_as_bool (cres
, NULL
))
688 value_release (cres
);
689 gnm_expr_top_unref (texpr
);
692 if (nok
< opinfo
[v
->op
].ntrue
)
693 BARF (g_strdup_printf (_("%s is out of permitted range"),
694 value_peek_string (val
)));
696 return GNM_VALIDATION_STATUS_VALID
;
702 WorkbookControl
*wbc
;
704 GnmCellPos
const *pos
;
705 gboolean
*showed_dialog
;
706 ValidationStatus status
;
710 validation_eval_range_cb (GnmCellIter
const *iter
, validation_eval_t
*closure
)
712 ValidationStatus status
;
713 gboolean showed_dialog
;
714 GnmStyle
const *mstyle
= sheet_style_get
715 (closure
->sheet
, iter
->pp
.eval
.col
, iter
->pp
.eval
.row
);
717 if (mstyle
!= NULL
) {
718 status
= gnm_validation_eval (closure
->wbc
, mstyle
,
719 closure
->sheet
, &iter
->pp
.eval
,
721 if (closure
->showed_dialog
)
722 *closure
->showed_dialog
= *closure
->showed_dialog
|| showed_dialog
;
724 if (status
!= GNM_VALIDATION_STATUS_VALID
) {
725 closure
->status
= status
;
726 return VALUE_TERMINATE
;
734 gnm_validation_eval_range (WorkbookControl
*wbc
,
735 Sheet
*sheet
, GnmCellPos
const *pos
, GnmRange
const *r
,
736 gboolean
*showed_dialog
)
739 validation_eval_t closure
;
741 GnmValue
*cell_range
= value_new_cellrange_r (sheet
, r
);
744 closure
.sheet
= sheet
;
746 closure
.showed_dialog
= showed_dialog
;
747 closure
.status
= GNM_VALIDATION_STATUS_VALID
;
749 eval_pos_init_pos (&ep
, sheet
, pos
);
751 result
= workbook_foreach_cell_in_range (&ep
, cell_range
, CELL_ITER_ALL
,
752 (CellIterFunc
) validation_eval_range_cb
,
755 value_release (cell_range
);
758 return GNM_VALIDATION_STATUS_VALID
;
759 return closure
.status
;