1 /*
2 * style-conditions.c:
3 *
4 * Copyright (C) 2005-2007 Jody Goldberg (jody@gnome.org)
5 * Copyright (C) 2013-2014 Morten Welinder (terra@gnome.org)
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) version 3.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA
21 */
22 #include <gnumeric-config.h>
23 #include <gnumeric.h>
24 #include <style-conditions.h>
25 #include <mstyle.h>
26 #include <expr.h>
27 #include <expr-impl.h>
28 #include <cell.h>
29 #include <value.h>
30 #include <sheet.h>
31 #include <gsf/gsf-impl-utils.h>
32 #include <string.h>
33 #include <func.h>
34 #include <gutils.h>
35
36 typedef GObjectClass GnmStyleConditionsClass;
37 struct _GnmStyleConditions {
38 GObject base;
39 GPtrArray *conditions;
40 Sheet *sheet;
41 };
42
43 static GObjectClass *parent_class;
44
45 static gboolean
debug_style_conds(void)46 debug_style_conds (void)
47 {
48 static int debug = -1;
49 if (debug < 0)
50 debug = gnm_debug_flag ("style-conds");
51 return debug;
52 }
53
54 // ----------------------------------------------------------------------------
55
56 static guint gscd_get_dep_type (void);
57
58 // ----------------------------------------------------------------------------
59
60 static unsigned
gnm_style_cond_op_operands(GnmStyleCondOp op)61 gnm_style_cond_op_operands (GnmStyleCondOp op)
62 {
63 switch (op) {
64 case GNM_STYLE_COND_BETWEEN:
65 case GNM_STYLE_COND_NOT_BETWEEN:
66 return 2;
67
68 case GNM_STYLE_COND_EQUAL:
69 case GNM_STYLE_COND_NOT_EQUAL:
70 case GNM_STYLE_COND_GT:
71 case GNM_STYLE_COND_LT:
72 case GNM_STYLE_COND_GTE:
73 case GNM_STYLE_COND_LTE:
74 case GNM_STYLE_COND_CUSTOM:
75 case GNM_STYLE_COND_CONTAINS_STR:
76 case GNM_STYLE_COND_NOT_CONTAINS_STR:
77 case GNM_STYLE_COND_BEGINS_WITH_STR:
78 case GNM_STYLE_COND_NOT_BEGINS_WITH_STR:
79 case GNM_STYLE_COND_ENDS_WITH_STR:
80 case GNM_STYLE_COND_NOT_ENDS_WITH_STR:
81 return 1;
82
83 case GNM_STYLE_COND_CONTAINS_ERR:
84 case GNM_STYLE_COND_NOT_CONTAINS_ERR:
85 case GNM_STYLE_COND_CONTAINS_BLANKS:
86 case GNM_STYLE_COND_NOT_CONTAINS_BLANKS:
87 return 0;
88 }
89 g_assert_not_reached ();
90 }
91
92 /**
93 * gnm_style_cond_is_valid:
94 * @cond: #GnmStyleCond
95 *
96 * Returns: %TRUE if @cond is in a reasonable state
97 **/
98 gboolean
gnm_style_cond_is_valid(GnmStyleCond const * cond)99 gnm_style_cond_is_valid (GnmStyleCond const *cond)
100 {
101 unsigned ui, N;
102
103 g_return_val_if_fail (cond != NULL, FALSE);
104
105 if (cond->overlay == NULL)
106 return FALSE;
107 if ((unsigned)cond->op > (unsigned)GNM_STYLE_COND_NOT_CONTAINS_BLANKS ||
108 (cond->op > GNM_STYLE_COND_CUSTOM && cond->op < GNM_STYLE_COND_CONTAINS_STR))
109 return FALSE;
110
111 N = gnm_style_cond_op_operands (cond->op);
112 for (ui = 0; ui < G_N_ELEMENTS (cond->deps); ui++) {
113 gboolean need = (ui < N);
114 gboolean have = (cond->deps[ui].base.texpr != NULL);
115 if (have != need)
116 return FALSE;
117 }
118
119 return TRUE;
120 }
121
122 GnmStyleCond *
gnm_style_cond_new(GnmStyleCondOp op,Sheet * sheet)123 gnm_style_cond_new (GnmStyleCondOp op, Sheet *sheet)
124 {
125 GnmStyleCond *res;
126 unsigned ui;
127
128 g_return_val_if_fail (IS_SHEET (sheet), NULL);
129
130 res = g_new0 (GnmStyleCond, 1);
131 res->op = op;
132 for (ui = 0; ui < 2; ui++) {
133 res->deps[ui].base.flags = gscd_get_dep_type ();
134 res->deps[ui].base.sheet = sheet;
135 }
136 return res;
137 }
138
139 /**
140 * gnm_style_cond_dup_to:
141 * @src: #GnmStyleCond
142 * @sheet: Sheet that the duplicate should live on
143 *
144 * Returns: (transfer full): the newly allocated #GnmStyleCond.
145 **/
146 static GnmStyleCond *
gnm_style_cond_dup_to(GnmStyleCond const * src,Sheet * sheet)147 gnm_style_cond_dup_to (GnmStyleCond const *src, Sheet *sheet)
148 {
149 GnmStyleCond *dst;
150 unsigned ui;
151
152 g_return_val_if_fail (src != NULL, NULL);
153
154 dst = gnm_style_cond_new (src->op, sheet);
155 gnm_style_cond_set_overlay (dst, src->overlay);
156 for (ui = 0; ui < 2; ui++)
157 gnm_style_cond_set_expr (dst, src->deps[ui].base.texpr, ui);
158
159 return dst;
160 }
161
162 /**
163 * gnm_style_cond_dup:
164 * @src: #GnmStyleCond
165 *
166 * Returns: (transfer full): the newly allocated #GnmStyleCond.
167 **/
168 static GnmStyleCond *
gnm_style_cond_dup(GnmStyleCond const * src)169 gnm_style_cond_dup (GnmStyleCond const *src)
170 {
171 g_return_val_if_fail (src != NULL, NULL);
172
173 return gnm_style_cond_dup_to (src, gnm_style_cond_get_sheet (src));
174 }
175
176 void
gnm_style_cond_free(GnmStyleCond * cond)177 gnm_style_cond_free (GnmStyleCond *cond)
178 {
179 unsigned ui;
180
181 g_return_if_fail (cond != NULL);
182
183 /* Be very careful: this is called for invalid conditions too */
184 if (cond->overlay)
185 gnm_style_unref (cond->overlay);
186 for (ui = 0; ui < 2; ui++)
187 gnm_style_cond_set_expr (cond, NULL, ui);
188
189 g_free (cond);
190 }
191
192 GType
gnm_style_cond_get_type(void)193 gnm_style_cond_get_type (void)
194 {
195 static GType t = 0;
196
197 if (t == 0) {
198 t = g_boxed_type_register_static ("GnmStyleCond",
199 (GBoxedCopyFunc)gnm_style_cond_dup,
200 (GBoxedFreeFunc)gnm_style_cond_free);
201 }
202 return t;
203 }
204
205 /**
206 * gnm_style_cond_get_sheet:
207 * @cond: #GnmStyleCond
208 *
209 * Returns: (transfer none): the #Sheet.
210 **/
211 Sheet *
gnm_style_cond_get_sheet(GnmStyleCond const * cond)212 gnm_style_cond_get_sheet (GnmStyleCond const *cond)
213 {
214 g_return_val_if_fail (cond != NULL, NULL);
215 return cond->deps[0].base.sheet;
216 }
217
218 /**
219 * gnm_style_cond_get_expr:
220 * @cond: #GnmStyleCond
221 * @idx: index
222 *
223 * Returns: (transfer none): the #GnmExprTop for the @idx'th condition.
224 **/
225 GnmExprTop const *
gnm_style_cond_get_expr(GnmStyleCond const * cond,unsigned idx)226 gnm_style_cond_get_expr (GnmStyleCond const *cond, unsigned idx)
227 {
228 g_return_val_if_fail (cond != NULL, NULL);
229 g_return_val_if_fail (idx < G_N_ELEMENTS (cond->deps), NULL);
230
231 return cond->deps[idx].base.texpr;
232 }
233
234 void
gnm_style_cond_set_expr(GnmStyleCond * cond,GnmExprTop const * texpr,unsigned idx)235 gnm_style_cond_set_expr (GnmStyleCond *cond,
236 GnmExprTop const *texpr,
237 unsigned idx)
238 {
239 g_return_if_fail (cond != NULL);
240 g_return_if_fail (idx < G_N_ELEMENTS (cond->deps));
241
242 dependent_set_expr (&cond->deps[idx].base, texpr);
243 if (texpr)
244 dependent_link (&cond->deps[idx].base);
245 }
246
247 void
gnm_style_cond_set_overlay(GnmStyleCond * cond,GnmStyle * overlay)248 gnm_style_cond_set_overlay (GnmStyleCond *cond, GnmStyle *overlay)
249 {
250 g_return_if_fail (cond != NULL);
251
252 if (overlay)
253 gnm_style_ref (overlay);
254 if (cond->overlay)
255 gnm_style_unref (cond->overlay);
256 cond->overlay = overlay;
257 }
258
259 static GnmExpr const *
generate_end_match(const char * endfunc,gboolean force,gboolean negate,GnmExprTop const * sexpr,GnmCellRef * cr)260 generate_end_match (const char *endfunc, gboolean force, gboolean negate,
261 GnmExprTop const *sexpr, GnmCellRef *cr)
262 {
263 GnmValue const *v = gnm_expr_get_constant (sexpr->expr);
264 GnmExpr const *len_expr;
265
266 if (v && VALUE_IS_STRING (v)) {
267 int len = g_utf8_strlen (value_peek_string (v), -1);
268 len_expr = gnm_expr_new_constant (value_new_int (len));
269 } else if (force) {
270 /*
271 * This is imperfect because the expression gets
272 * evaluated twice.
273 */
274 len_expr = gnm_expr_new_funcall1
275 (gnm_func_lookup_or_add_placeholder ("LEN"),
276 gnm_expr_copy (sexpr->expr));
277 } else
278 return NULL;
279
280 return gnm_expr_new_binary
281 (gnm_expr_new_funcall2
282 (gnm_func_lookup_or_add_placeholder (endfunc),
283 gnm_expr_new_cellref (cr),
284 len_expr),
285 negate ? GNM_EXPR_OP_NOT_EQUAL : GNM_EXPR_OP_EQUAL,
286 gnm_expr_copy (sexpr->expr));
287 }
288
289
290 /**
291 * gnm_style_cond_get_alternate_expr:
292 * @cond: condition
293 *
294 * Returns: (transfer full) (allow-none): An custom expression that can be
295 * used in place of @cond.
296 **/
297 GnmExprTop const *
gnm_style_cond_get_alternate_expr(GnmStyleCond const * cond)298 gnm_style_cond_get_alternate_expr (GnmStyleCond const *cond)
299 {
300 GnmCellRef self;
301 GnmExpr const *expr;
302 gboolean negate = FALSE;
303 GnmExprTop const *sexpr = NULL;
304
305 g_return_val_if_fail (cond != NULL, NULL);
306
307 gnm_cellref_init (&self, NULL, 0, 0, TRUE);
308
309 if (gnm_style_cond_op_operands (cond->op) > 0) {
310 sexpr = gnm_style_cond_get_expr (cond, 0);
311 if (!sexpr)
312 return NULL;
313 }
314
315 switch (cond->op) {
316 case GNM_STYLE_COND_NOT_CONTAINS_ERR:
317 negate = TRUE; /* ...and fall through */
318 case GNM_STYLE_COND_CONTAINS_ERR:
319 expr = gnm_expr_new_funcall1
320 (gnm_func_lookup_or_add_placeholder ("ISERROR"),
321 gnm_expr_new_cellref (&self));
322 break;
323
324 case GNM_STYLE_COND_CONTAINS_STR:
325 negate = TRUE; /* ...and fall through */
326 case GNM_STYLE_COND_NOT_CONTAINS_STR:
327 expr = gnm_expr_new_funcall1
328 (gnm_func_lookup_or_add_placeholder ("ISERROR"),
329 gnm_expr_new_funcall2
330 (gnm_func_lookup_or_add_placeholder ("FIND"),
331 gnm_expr_copy (sexpr->expr),
332 gnm_expr_new_cellref (&self)));
333 break;
334
335 case GNM_STYLE_COND_NOT_CONTAINS_BLANKS:
336 negate = TRUE; /* ...and fall through */
337 case GNM_STYLE_COND_CONTAINS_BLANKS:
338 /* This means blanks-only */
339
340 expr = gnm_expr_new_binary
341 (gnm_expr_new_funcall1
342 (gnm_func_lookup_or_add_placeholder ("LEN"),
343 gnm_expr_new_funcall1
344 (gnm_func_lookup_or_add_placeholder ("TRIM"),
345 gnm_expr_new_cellref (&self))),
346 negate ? GNM_EXPR_OP_GT : GNM_EXPR_OP_EQUAL,
347 gnm_expr_new_constant (value_new_int (0)));
348 negate = FALSE;
349 break;
350
351 case GNM_STYLE_COND_NOT_BEGINS_WITH_STR:
352 negate = TRUE; /* ...and fall through */
353 case GNM_STYLE_COND_BEGINS_WITH_STR:
354 /*
355 * We are constrained by using only Excel functions and not
356 * evaluating the needle more than once. We cannot fulfill
357 * that and end up computing the needle twice.
358 */
359 expr = generate_end_match ("LEFT", TRUE, negate, sexpr, &self);
360 negate = FALSE;
361 break;
362
363 case GNM_STYLE_COND_NOT_ENDS_WITH_STR:
364 negate = TRUE; /* ...and fall through */
365 case GNM_STYLE_COND_ENDS_WITH_STR:
366 /*
367 * We are constrained by using only Excel functions and not
368 * evaluating the needle more than once. We cannot fulfill
369 * that and end up computing the needle twice.
370 */
371 expr = generate_end_match ("RIGHT", TRUE, negate, sexpr, &self);
372 negate = FALSE;
373 break;
374
375 default:
376 return NULL;
377 }
378
379 if (negate)
380 expr = gnm_expr_new_funcall1
381 (gnm_func_lookup_or_add_placeholder ("NOT"), expr);
382
383 return gnm_expr_top_new (expr);
384 }
385
386 static gboolean
isself(GnmExpr const * expr)387 isself (GnmExpr const *expr)
388 {
389 GnmCellRef const *cr = gnm_expr_get_cellref (expr);
390
391 return (cr &&
392 cr->sheet == NULL &&
393 cr->col == 0 && cr->row == 0 &&
394 cr->col_relative && cr->row_relative);
395 }
396
397 static GnmExprTop const *
decode_end_match(const char * endfunc,GnmExpr const * expr,gboolean * negated)398 decode_end_match (const char *endfunc, GnmExpr const *expr, gboolean *negated)
399 {
400 GnmExpr const *needle;
401 GnmExpr const *expr2;
402
403 *negated = (GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_NOT_EQUAL);
404
405 if ((GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_EQUAL ||
406 GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_NOT_EQUAL) &&
407 (needle = expr->binary.value_b) &&
408 (expr2 = expr->binary.value_a) &&
409 GNM_EXPR_GET_OPER (expr2) == GNM_EXPR_OP_FUNCALL &&
410 expr2->func.argc == 2 &&
411 expr2->func.func == gnm_func_lookup_or_add_placeholder (endfunc) &&
412 isself (expr2->func.argv[0])) {
413 GnmExpr const *len_expr = expr2->func.argv[1];
414 GnmValue const *v, *vl;
415
416 if (GNM_EXPR_GET_OPER (len_expr) == GNM_EXPR_OP_FUNCALL &&
417 len_expr->func.argc == 1 &&
418 len_expr->func.func == gnm_func_lookup_or_add_placeholder ("LEN") &&
419 gnm_expr_equal (len_expr->func.argv[0], needle))
420 return gnm_expr_top_new (gnm_expr_copy (needle));
421
422 if ((v = gnm_expr_get_constant (needle)) &&
423 VALUE_IS_STRING (v) &&
424 (vl = gnm_expr_get_constant (len_expr)) &&
425 VALUE_IS_NUMBER (vl) &&
426 value_get_as_float (vl) == g_utf8_strlen (value_peek_string (v), -1))
427 return gnm_expr_top_new (gnm_expr_copy (needle));
428 }
429
430 return NULL;
431 }
432
433 /**
434 * gnm_style_cond_canonicalize:
435 * @cond: condition
436 *
437 * Turns a custom condition into a more specific one, i.e., reverses the
438 * effect of using gnm_style_cond_get_alternate_expr. Leaves the condition
439 * alone if it is not recognized.
440 **/
441 void
gnm_style_cond_canonicalize(GnmStyleCond * cond)442 gnm_style_cond_canonicalize (GnmStyleCond *cond)
443 {
444 GnmExpr const *expr, *expr2;
445 GnmExprTop const *texpr;
446 GnmValue const *v;
447 gboolean negate = FALSE;
448 gboolean match_negated;
449 GnmFunc const *iserror;
450 GnmFunc const *iferror;
451 GnmFunc const *find;
452 GnmStyleCondOp newop = GNM_STYLE_COND_CUSTOM;
453
454 g_return_if_fail (cond != NULL);
455
456 if (cond->op != GNM_STYLE_COND_CUSTOM)
457 return;
458
459 texpr = gnm_style_cond_get_expr (cond, 0);
460 if (!texpr)
461 return;
462 expr = texpr->expr;
463 texpr = NULL;
464
465 if (GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_FUNCALL &&
466 expr->func.argc == 1 &&
467 expr->func.func == gnm_func_lookup_or_add_placeholder ("NOT")) {
468 negate = TRUE;
469 expr = expr->func.argv[0];
470 }
471
472 iserror = gnm_func_lookup_or_add_placeholder ("ISERROR");
473 iferror = gnm_func_lookup_or_add_placeholder ("IFERROR");
474 find = gnm_func_lookup_or_add_placeholder ("FIND");
475
476 if (GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_FUNCALL &&
477 expr->func.argc == 1 && expr->func.func == iserror &&
478 isself (expr->func.argv[0])) {
479 newop = negate
480 ? GNM_STYLE_COND_NOT_CONTAINS_ERR
481 : GNM_STYLE_COND_CONTAINS_ERR;
482 } else if (GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_FUNCALL &&
483 expr->func.argc == 1 && expr->func.func == iserror &&
484 (expr2 = expr->func.argv[0]) &&
485 GNM_EXPR_GET_OPER (expr2) == GNM_EXPR_OP_FUNCALL &&
486 expr2->func.argc == 2 && expr2->func.func == find &&
487 isself (expr2->func.argv[1])) {
488 texpr = gnm_expr_top_new (gnm_expr_copy (expr2->func.argv[0]));
489 newop = negate
490 ? GNM_STYLE_COND_CONTAINS_STR
491 : GNM_STYLE_COND_NOT_CONTAINS_STR;
492 } else if ((GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_EQUAL ||
493 GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_GT) &&
494 (v = gnm_expr_get_constant (expr->binary.value_b)) &&
495 VALUE_IS_FLOAT (v) && value_get_as_float (v) == 0 &&
496 (expr2 = expr->binary.value_a) &&
497 GNM_EXPR_GET_OPER (expr2) == GNM_EXPR_OP_FUNCALL &&
498 expr2->func.argc == 1 &&
499 expr2->func.func == gnm_func_lookup_or_add_placeholder ("LEN") &&
500 (expr2 = expr2->func.argv[0]) &&
501 GNM_EXPR_GET_OPER (expr2) == GNM_EXPR_OP_FUNCALL &&
502 expr2->func.argc == 1 &&
503 expr2->func.func == gnm_func_lookup_or_add_placeholder ("TRIM") &&
504 isself (expr2->func.argv[0])) {
505 if (GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_GT)
506 negate = !negate;
507
508 newop = negate
509 ? GNM_STYLE_COND_NOT_CONTAINS_BLANKS
510 : GNM_STYLE_COND_CONTAINS_BLANKS;
511 } else if (GNM_EXPR_GET_OPER (expr) == GNM_EXPR_OP_EQUAL &&
512 (v = gnm_expr_get_constant (expr->binary.value_b)) &&
513 VALUE_IS_FLOAT (v) && value_get_as_float (v) == 1 &&
514 (expr2 = expr->binary.value_a) &&
515 GNM_EXPR_GET_OPER (expr2) == GNM_EXPR_OP_FUNCALL &&
516 expr2->func.argc == 2 && expr2->func.func == iferror &&
517 (v = gnm_expr_get_constant (expr2->func.argv[1])) &&
518 VALUE_IS_FLOAT (v) && value_get_as_float (v) != 1 &&
519 (expr2 = expr2->func.argv[0]) &&
520 GNM_EXPR_GET_OPER (expr2) == GNM_EXPR_OP_FUNCALL &&
521 expr2->func.argc == 2 && expr2->func.func == find &&
522 isself (expr2->func.argv[1])) {
523 texpr = gnm_expr_top_new (gnm_expr_copy (expr2->func.argv[0]));
524 newop = negate
525 ? GNM_STYLE_COND_NOT_BEGINS_WITH_STR
526 : GNM_STYLE_COND_BEGINS_WITH_STR;
527 } else if ((texpr = decode_end_match ("LEFT", expr, &match_negated))) {
528 newop = (negate ^ match_negated)
529 ? GNM_STYLE_COND_NOT_BEGINS_WITH_STR
530 : GNM_STYLE_COND_BEGINS_WITH_STR;
531 } else if ((texpr = decode_end_match ("RIGHT", expr, &match_negated))) {
532 newop = (negate ^ match_negated)
533 ? GNM_STYLE_COND_NOT_ENDS_WITH_STR
534 : GNM_STYLE_COND_ENDS_WITH_STR;
535 }
536
537 if (newop != GNM_STYLE_COND_CUSTOM) {
538 gnm_style_cond_set_expr (cond, texpr, 0);
539 if (texpr)
540 gnm_expr_top_unref (texpr);
541 cond->op = newop;
542 }
543 }
544
545 static gboolean
case_insensitive_has_fix(GnmValue const * vs,GnmValue const * vp,gboolean is_prefix)546 case_insensitive_has_fix (GnmValue const *vs, GnmValue const *vp,
547 gboolean is_prefix)
548 {
549 size_t plen = g_utf8_strlen (value_peek_string (vp), -1);
550 const char *s = value_peek_string (vs);
551 size_t slen = g_utf8_strlen (s, -1);
552 GnmValue *vs2;
553 gboolean res;
554
555 if (plen > slen)
556 return FALSE;
557
558 vs2 = value_new_string_nocopy
559 (is_prefix
560 ? g_strndup (s, g_utf8_offset_to_pointer (s, plen) - s)
561 : g_strdup (g_utf8_offset_to_pointer (s, slen - plen)));
562 res = (value_compare (vs2, vp, FALSE) == IS_EQUAL);
563 value_release (vs2);
564
565 return res;
566 }
567
568
569 static gboolean
gnm_style_cond_eval(GnmStyleCond * cond,GnmValue const * cv,GnmEvalPos const * ep)570 gnm_style_cond_eval (GnmStyleCond *cond, GnmValue const *cv,
571 GnmEvalPos const *ep)
572 {
573 gboolean negate = FALSE;
574 gboolean res;
575 GnmValue *val0 = NULL;
576 GnmValue *val1 = NULL;
577 GnmEvalPos epos = *ep;
578
579 switch (gnm_style_cond_op_operands (cond->op)) {
580 case 2:
581 epos.dep = &cond->deps[1].base;
582 val1 = gnm_expr_top_eval (cond->deps[1].base.texpr, &epos,
583 GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
584 /* Fall through */
585 case 1:
586 epos.dep = &cond->deps[0].base;
587 val0 = gnm_expr_top_eval (cond->deps[0].base.texpr, &epos,
588 GNM_EXPR_EVAL_SCALAR_NON_EMPTY);
589 /* Fall through */
590 case 0:
591 break;
592 default:
593 g_assert_not_reached ();
594 }
595
596 switch (cond->op) {
597 case GNM_STYLE_COND_NOT_EQUAL:
598 negate = TRUE; /* ...and fall through */
599 case GNM_STYLE_COND_EQUAL:
600 res = value_compare (cv, val0, FALSE) == IS_EQUAL;
601 break;
602
603 case GNM_STYLE_COND_LTE:
604 negate = TRUE; /* ...and fall through */
605 case GNM_STYLE_COND_GT:
606 res = value_compare (cv, val0, FALSE) == IS_GREATER;
607 break;
608
609 case GNM_STYLE_COND_GTE:
610 negate = TRUE; /* ...and fall through */
611 case GNM_STYLE_COND_LT:
612 res = value_compare (cv, val0, FALSE) == IS_LESS;
613 break;
614
615 case GNM_STYLE_COND_NOT_BETWEEN:
616 negate = TRUE; /* ...and fall through */
617 case GNM_STYLE_COND_BETWEEN:
618 res = !(value_compare (cv, val0, FALSE) == IS_LESS ||
619 value_compare (cv, val1, FALSE) == IS_GREATER);
620 break;
621
622 case GNM_STYLE_COND_NOT_CONTAINS_ERR:
623 negate = TRUE; /* ...and fall through */
624 case GNM_STYLE_COND_CONTAINS_ERR:
625 res = cv && VALUE_IS_ERROR (cv);
626 break;
627
628 case GNM_STYLE_COND_NOT_CONTAINS_BLANKS:
629 negate = TRUE; /* ...and fall through */
630 case GNM_STYLE_COND_CONTAINS_BLANKS: {
631 const char *s = cv ? value_peek_string (cv) : "";
632 while (*s) {
633 gunichar uc = g_utf8_get_char (s);
634 if (!g_unichar_isspace (uc))
635 break;
636 s = g_utf8_next_char (s);
637 }
638 res = (*s == 0);
639 break;
640 }
641
642 case GNM_STYLE_COND_NOT_CONTAINS_STR:
643 negate = TRUE; /* ...and fall through */
644 case GNM_STYLE_COND_CONTAINS_STR:
645 res = (cv &&
646 gnm_excel_search_impl (value_peek_string (val0),
647 value_peek_string (cv),
648 0) >= 0);
649 break;
650
651 case GNM_STYLE_COND_NOT_BEGINS_WITH_STR:
652 negate = TRUE; /* ...and fall through */
653 case GNM_STYLE_COND_BEGINS_WITH_STR:
654 res = (cv && case_insensitive_has_fix (cv, val0, TRUE));
655 break;
656
657 case GNM_STYLE_COND_NOT_ENDS_WITH_STR:
658 negate = TRUE; /* ...and fall through */
659 case GNM_STYLE_COND_ENDS_WITH_STR:
660 res = (cv && case_insensitive_has_fix (cv, val0, FALSE));
661 break;
662
663 case GNM_STYLE_COND_CUSTOM:
664 res = value_get_as_bool (val0, NULL);
665 break;
666
667 default:
668 g_assert_not_reached ();
669 }
670
671 value_release (val0);
672 value_release (val1);
673
674 return negate ? !res : res;
675 }
676
677 static gboolean
gnm_style_cond_equal(GnmStyleCond const * ca,GnmStyleCond const * cb,gboolean relax_sheet)678 gnm_style_cond_equal (GnmStyleCond const *ca, GnmStyleCond const *cb,
679 gboolean relax_sheet)
680 {
681 unsigned oi, N;
682
683 if (ca->op != cb->op)
684 return FALSE;
685
686 if (!gnm_style_equal (ca->overlay, cb->overlay))
687 return FALSE;
688
689 N = gnm_style_cond_op_operands (ca->op);
690 for (oi = 0; oi < N; oi++) {
691 if (!relax_sheet && ca->deps[oi].base.sheet != cb->deps[oi].base.sheet)
692 return FALSE;
693 if (!gnm_expr_top_equal (ca->deps[oi].base.texpr,
694 cb->deps[oi].base.texpr))
695 return FALSE;
696 }
697
698 return TRUE;
699 }
700
701 static void
gnm_style_cond_set_pos(GnmStyleCond * sc,GnmCellPos const * pos)702 gnm_style_cond_set_pos (GnmStyleCond *sc, GnmCellPos const *pos)
703 {
704 unsigned oi, N;
705
706 N = gnm_style_cond_op_operands (sc->op);
707 for (oi = 0; oi < N; oi++) {
708 gboolean qlink = dependent_is_linked (&sc->deps[oi].base);
709 if (qlink)
710 dependent_unlink (&sc->deps[oi].base);
711 sc->deps[oi].pos = *pos;
712 if (qlink)
713 dependent_link (&sc->deps[oi].base);
714 }
715 }
716
717 // For debugging purposes
718 char *
gnm_style_cond_as_string(GnmStyleCond const * cond)719 gnm_style_cond_as_string (GnmStyleCond const *cond)
720 {
721 unsigned oi, N;
722 static const char * const ops[] = {
723 "between", "not-between",
724 "equal", "not-equal",
725 "greater-than", "less-then",
726 "greater-than-or-equal", "less-than-or-equal",
727 "is-true",
728 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
729 "contains", "does-not-contain",
730 "begins-with", "does-not-begin-with",
731 "end-with", "does-not-end-with",
732 "is-error", "is-not-error",
733 "contains-blank", "does-not-contain-blank"
734 };
735 GString *str = g_string_new (ops[cond->op]);
736 Sheet *sheet = gnm_style_cond_get_sheet (cond);
737 GnmConventions const *convs = sheet_get_conventions (sheet);
738
739 N = gnm_style_cond_op_operands (cond->op);
740 for (oi = 0; oi < N; oi++) {
741 char *s;
742 GnmParsePos pp;
743
744 parse_pos_init_dep (&pp, &cond->deps[oi].base);
745 s = gnm_expr_top_as_string (gnm_style_cond_get_expr (cond, oi),
746 &pp,
747 convs);
748 g_string_append_c (str, ' ');
749 g_string_append (str, s);
750 g_free (s);
751 }
752 return g_string_free (str, FALSE);
753 }
754
755 // ----------------------------------------------------------------------------
756
757 static void
gscd_eval(GnmDependent * dep)758 gscd_eval (GnmDependent *dep)
759 {
760 // Nothing yet
761 }
762
763 static GSList *
gscd_changed(GnmDependent * dep)764 gscd_changed (GnmDependent *dep)
765 {
766 GnmStyleCondDep const *scd = (GnmStyleCondDep const *)dep;
767 if (debug_style_conds ()) {
768 g_printerr ("Changed StyleCondDep/%p\n", dep);
769 }
770 return scd->dep_cont ? g_slist_prepend (NULL, scd->dep_cont) : NULL;
771 }
772
773 static GnmCellPos *
gscd_pos(GnmDependent const * dep)774 gscd_pos (GnmDependent const *dep)
775 {
776 return &((GnmStyleCondDep *)dep)->pos;
777 }
778
779 static void
gscd_debug_name(GnmDependent const * dep,GString * target)780 gscd_debug_name (GnmDependent const *dep, GString *target)
781 {
782 g_string_append_printf (target, "StyleCondDep/%p", (void *)dep);
783 }
784
785
786 static DEPENDENT_MAKE_TYPE(gscd, .eval = gscd_eval, .changed = gscd_changed, .pos = gscd_pos, .debug_name = gscd_debug_name)
787
788 // ----------------------------------------------------------------------------
789
790 static void
gnm_style_conditions_finalize(GObject * obj)791 gnm_style_conditions_finalize (GObject *obj)
792 {
793 GnmStyleConditions *sc = (GnmStyleConditions *)obj;
794
795 while (sc->conditions)
796 gnm_style_conditions_delete (sc, sc->conditions->len - 1);
797 G_OBJECT_CLASS (parent_class)->finalize (obj);
798 }
799
800 static void
gnm_style_conditions_init(GnmStyleConditions * sc)801 gnm_style_conditions_init (GnmStyleConditions *sc)
802 {
803 sc->conditions = NULL;
804 }
805
806 static void
gnm_style_conditions_class_init(GObjectClass * gobject_class)807 gnm_style_conditions_class_init (GObjectClass *gobject_class)
808 {
809 parent_class = g_type_class_peek_parent (gobject_class);
810 gobject_class->finalize = gnm_style_conditions_finalize;
811 }
GSF_CLASS(GnmStyleConditions,gnm_style_conditions,gnm_style_conditions_class_init,gnm_style_conditions_init,G_TYPE_OBJECT)812 GSF_CLASS (GnmStyleConditions, gnm_style_conditions,
813 gnm_style_conditions_class_init, gnm_style_conditions_init,
814 G_TYPE_OBJECT)
815
816 /**
817 * gnm_style_conditions_new:
818 * @sheet: #Sheet
819 *
820 * Convenience tool to create a #GnmStyleCondition. Straight g_object_new
821 * will work too.
822 *
823 * Returns: (transfer full): a #GnmStyleConditions
824 **/
825 GnmStyleConditions *
826 gnm_style_conditions_new (Sheet *sheet)
827 {
828 GnmStyleConditions *res;
829 g_return_val_if_fail (IS_SHEET (sheet), NULL);
830
831 res = g_object_new (gnm_style_conditions_get_type (), NULL);
832 res->sheet = sheet;
833 return res;
834 }
835
836 /**
837 * gnm_style_conditions_dup_to:
838 * @sc: (nullable): the #GnmStyleConditions to duplicate.
839 * @sheet: Sheet that the duplicate should live on
840 *
841 * Returns: (transfer full) (nullable): the duplicated #GnmStyleConditions.
842 **/
843 GnmStyleConditions *
gnm_style_conditions_dup_to(GnmStyleConditions const * sc,Sheet * sheet)844 gnm_style_conditions_dup_to (GnmStyleConditions const *sc, Sheet *sheet)
845 {
846 GnmStyleConditions *dup;
847 GPtrArray const *ga;
848 if (sc == NULL)
849 return NULL;
850
851 dup = gnm_style_conditions_new (sheet);
852 ga = gnm_style_conditions_details (sc);
853 if (ga != NULL) {
854 guint i;
855 GPtrArray *ga_dup = g_ptr_array_sized_new (ga->len);
856 for (i = 0; i < ga->len; i++) {
857 GnmStyleCond *cond = g_ptr_array_index (ga, i);
858 g_ptr_array_add (ga_dup, gnm_style_cond_dup_to (cond, sheet));
859 }
860 dup->conditions = ga_dup;
861 }
862 return dup;
863 }
864
865
866 /**
867 * gnm_style_conditions_dup:
868 * @sc: (nullable): the #GnmStyleConditions to duplicate.
869 *
870 * Returns: (transfer full) (nullable): the duplicated #GnmStyleConditions.
871 **/
872 GnmStyleConditions *
gnm_style_conditions_dup(GnmStyleConditions const * sc)873 gnm_style_conditions_dup (GnmStyleConditions const *sc)
874 {
875 return sc
876 ? gnm_style_conditions_dup_to (sc, gnm_style_conditions_get_sheet (sc))
877 : NULL;
878 }
879
880 #define MIX(H) do { \
881 H *= G_GUINT64_CONSTANT(123456789012345); \
882 H ^= (H >> 31); \
883 } while (0)
884
885 guint32
gnm_style_conditions_hash(GnmStyleConditions const * sc)886 gnm_style_conditions_hash (GnmStyleConditions const *sc)
887 {
888 guint64 hash = 42;
889 GPtrArray const *ga;
890 unsigned ui;
891
892 /*
893 * Note: this hash must not depend on the expressions stored
894 * in ->deps. And probably not on the sheet either.
895 */
896
897 g_return_val_if_fail (sc != NULL, 0u);
898
899 ga = gnm_style_conditions_details (sc);
900 for (ui = 0; ui < (ga ? ga->len : 0u); ui++) {
901 GnmStyleCond *cond = g_ptr_array_index (ga, ui);
902 if (cond->overlay)
903 hash ^= gnm_style_hash_XL (cond->overlay);
904 MIX (hash);
905 hash ^= cond->op;
906 MIX (hash);
907 }
908
909 return hash;
910 }
911
912 #undef MIX
913
914 /**
915 * gnm_style_conditions_equal:
916 * @sca: first #GnmStyleConditions to compare.
917 * @scb: second #GnmStyleConditions to compare.
918 * @relax_sheet: if %TRUE, ignore differences solely caused by being linked into different sheets.
919 *
920 * Returns: %TRUE if the conditions are equal.
921 **/
922 gboolean
gnm_style_conditions_equal(GnmStyleConditions const * sca,GnmStyleConditions const * scb,gboolean relax_sheet)923 gnm_style_conditions_equal (GnmStyleConditions const *sca,
924 GnmStyleConditions const *scb,
925 gboolean relax_sheet)
926 {
927 GPtrArray const *ga, *gb;
928 unsigned ui;
929
930 g_return_val_if_fail (sca != NULL, FALSE);
931 g_return_val_if_fail (scb != NULL, FALSE);
932
933 if (!relax_sheet && sca->sheet != scb->sheet)
934 return FALSE;
935
936 ga = gnm_style_conditions_details (sca);
937 gb = gnm_style_conditions_details (scb);
938 if (!ga || !gb)
939 return ga == gb;
940 if (ga->len != gb->len)
941 return FALSE;
942
943 for (ui = 0; ui < ga->len; ui++) {
944 GnmStyleCond const *ca = g_ptr_array_index (ga, ui);
945 GnmStyleCond const *cb = g_ptr_array_index (gb, ui);
946 if (!gnm_style_cond_equal (ca, cb, relax_sheet))
947 return FALSE;
948 }
949
950 return TRUE;
951 }
952
953
954 /**
955 * gnm_style_conditions_get_sheet:
956 * @sc: #GnmStyleConditions
957 *
958 * Returns: (transfer none): the #Sheet.
959 **/
960 Sheet *
gnm_style_conditions_get_sheet(GnmStyleConditions const * sc)961 gnm_style_conditions_get_sheet (GnmStyleConditions const *sc)
962 {
963 g_return_val_if_fail (sc != NULL, NULL);
964 return sc->sheet;
965 }
966
967 /**
968 * gnm_style_conditions_details:
969 * @sc: #GnmStyleConditions
970 *
971 * Returns: (element-type GnmStyleCond) (transfer none): style details.
972 **/
973 GPtrArray const *
gnm_style_conditions_details(GnmStyleConditions const * sc)974 gnm_style_conditions_details (GnmStyleConditions const *sc)
975 {
976 g_return_val_if_fail (sc != NULL, NULL);
977
978 return sc->conditions;
979 }
980
981 /**
982 * gnm_style_conditions_insert:
983 * @sc: #GnmStyleConditions
984 * @cond: #GnmStyleCond
985 * @pos: position.
986 *
987 * Insert @cond before @pos (append if @pos < 0).
988 **/
989 void
gnm_style_conditions_insert(GnmStyleConditions * sc,GnmStyleCond const * cond_,int pos)990 gnm_style_conditions_insert (GnmStyleConditions *sc,
991 GnmStyleCond const *cond_, int pos)
992 {
993 GnmStyleCond *cond;
994
995 g_return_if_fail (sc != NULL);
996 g_return_if_fail (cond_ != NULL);
997
998 g_return_if_fail (gnm_style_cond_is_valid (cond_));
999
1000 g_return_if_fail (gnm_style_conditions_get_sheet (sc) ==
1001 gnm_style_cond_get_sheet (cond_));
1002
1003 if (sc->conditions == NULL)
1004 sc->conditions = g_ptr_array_new ();
1005
1006 cond = gnm_style_cond_dup (cond_);
1007 g_ptr_array_add (sc->conditions, cond);
1008 if (pos >= 0) {
1009 int i;
1010
1011 for (i = sc->conditions->len - 1;
1012 i > pos;
1013 i--)
1014 g_ptr_array_index (sc->conditions, i) =
1015 g_ptr_array_index (sc->conditions, i - 1);
1016 g_ptr_array_index (sc->conditions, pos) = cond;
1017 }
1018 }
1019
1020 void
gnm_style_conditions_delete(GnmStyleConditions * sc,guint pos)1021 gnm_style_conditions_delete (GnmStyleConditions *sc, guint pos)
1022 {
1023 g_return_if_fail (sc != NULL);
1024 g_return_if_fail (sc->conditions != NULL);
1025 g_return_if_fail (sc->conditions->len > pos);
1026
1027 gnm_style_cond_free (g_ptr_array_index (sc->conditions, pos));
1028 if (sc->conditions->len <= 1) {
1029 g_ptr_array_free (sc->conditions, TRUE);
1030 sc->conditions = NULL;
1031 } else
1032 g_ptr_array_remove_index (sc->conditions, pos);
1033 }
1034
1035
1036 /**
1037 * gnm_style_conditions_overlay:
1038 * @sc: #GnmStyleConditions
1039 * @base: #GnmStyle
1040 *
1041 * Returns: (element-type GnmStyle) (transfer full): an array of #GnmStyle.
1042 **/
1043 GPtrArray *
gnm_style_conditions_overlay(GnmStyleConditions const * sc,GnmStyle const * base)1044 gnm_style_conditions_overlay (GnmStyleConditions const *sc,
1045 GnmStyle const *base)
1046 {
1047 GPtrArray *res;
1048 unsigned i;
1049
1050 g_return_val_if_fail (sc != NULL, NULL);
1051 g_return_val_if_fail (sc->conditions != NULL, NULL);
1052
1053 res = g_ptr_array_sized_new (sc->conditions->len);
1054 for (i = 0 ; i < sc->conditions->len; i++) {
1055 GnmStyleCond const *cond =
1056 g_ptr_array_index (sc->conditions, i);
1057 GnmStyle const *overlay = cond->overlay;
1058 GnmStyle *merge = gnm_style_new_merged (base, overlay);
1059 /* We only draw a background colour if the pattern != 0 */
1060 if (gnm_style_get_pattern (merge) == 0 &&
1061 gnm_style_is_element_set (overlay, MSTYLE_COLOR_BACK) &&
1062 !gnm_style_is_element_set (overlay, MSTYLE_PATTERN))
1063 gnm_style_set_pattern (merge, 1);
1064 g_ptr_array_add (res, merge);
1065 }
1066 return res;
1067 }
1068
1069 /**
1070 * gnm_style_conditions_eval:
1071 * @sc: #GnmStyleConditions
1072 * @pos: #GnmEvalPos
1073 *
1074 * Returns: the condition to use or -1 if none match.
1075 **/
1076 int
gnm_style_conditions_eval(GnmStyleConditions const * sc,GnmEvalPos const * ep)1077 gnm_style_conditions_eval (GnmStyleConditions const *sc, GnmEvalPos const *ep)
1078 {
1079 unsigned i;
1080 GPtrArray const *conds;
1081 GnmCell *cell;
1082 GnmValue *cv;
1083
1084 g_return_val_if_fail (sc != NULL, -1);
1085 g_return_val_if_fail (sc->conditions != NULL, -1);
1086
1087 cell = sheet_cell_get (ep->sheet, ep->eval.col, ep->eval.row);
1088 cv = cell ? value_dup (cell->value) : NULL;
1089
1090 conds = sc->conditions;
1091
1092 if (debug_style_conds ()) {
1093 GnmParsePos pp;
1094 parse_pos_init_evalpos (&pp, ep);
1095
1096 g_printerr ("Evaluating conditions %p at %s with %d clauses\n",
1097 sc,
1098 parsepos_as_string (&pp),
1099 conds->len);
1100 }
1101
1102 for (i = 0 ; i < conds->len ; i++) {
1103 GnmStyleCond *cond = g_ptr_array_index (conds, i);
1104 gboolean use_this = gnm_style_cond_eval (cond, cv, ep);
1105
1106 if (use_this) {
1107 if (debug_style_conds ())
1108 g_printerr (" Using clause %d\n", i);
1109 value_release (cv);
1110 return i;
1111 }
1112 }
1113
1114 if (debug_style_conds ())
1115 g_printerr (" No matching clauses\n");
1116
1117 value_release (cv);
1118 return -1;
1119 }
1120
1121
1122 /**
1123 * gnm_style_conditions_set_pos:
1124 * @sc: #GnmStyleConditions
1125 * @pos: new position
1126 *
1127 * Sets the position of @sc, i.e., the position at which relative addresses
1128 * in the conditions will be evaluated.
1129 **/
1130 void
gnm_style_conditions_set_pos(GnmStyleConditions * sc,GnmCellPos const * pos)1131 gnm_style_conditions_set_pos (GnmStyleConditions *sc,
1132 GnmCellPos const *pos)
1133 {
1134 GPtrArray const *ga;
1135 unsigned ui;
1136
1137 g_return_if_fail (sc != NULL);
1138
1139 ga = gnm_style_conditions_details (sc);
1140 for (ui = 0; ui < (ga ? ga->len : 0u); ui++) {
1141 GnmStyleCond *cond = g_ptr_array_index (ga, ui);
1142 gnm_style_cond_set_pos (cond, pos);
1143 }
1144 }
1145
1146 /**
1147 * gnm_style_conditions_get_pos:
1148 * @sc: #GnmStyleConditions
1149 *
1150 * Returns: (transfer none) (nullable): The position at which relative
1151 * addresses in the conditions will be evaluated. This may be %NULL if
1152 * no conditions require a position.
1153 **/
1154 GnmCellPos const *
gnm_style_conditions_get_pos(GnmStyleConditions const * sc)1155 gnm_style_conditions_get_pos (GnmStyleConditions const *sc)
1156 {
1157 GPtrArray const *ga;
1158 unsigned ui;
1159
1160 g_return_val_if_fail (sc != NULL, NULL);
1161
1162 ga = gnm_style_conditions_details (sc);
1163 for (ui = 0; ui < (ga ? ga->len : 0u); ui++) {
1164 GnmStyleCond *cond = g_ptr_array_index (ga, ui);
1165 int N = gnm_style_cond_op_operands (cond->op);
1166 if (N > 0)
1167 return dependent_pos (&cond->deps[0].base);
1168 }
1169 return NULL;
1170 }
1171