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