2 * search.c: Search-and-replace for Gnumeric.
4 * Copyright (C) 2001-2009 Morten Welinder (terra@gnome.org)
7 #include <gnumeric-config.h>
18 #include <number-match.h>
20 #include <sheet-object-cell-comment.h>
21 #include <gsf/gsf-impl-utils.h>
26 static GObjectClass
*parent_class
;
29 GOSearchReplaceClass base_class
;
30 } GnmSearchReplaceClass
;
36 PROP_SEARCH_OTHER_VALUES
,
37 PROP_SEARCH_EXPRESSIONS
,
38 PROP_SEARCH_EXPRESSION_RESULTS
,
44 PROP_REPLACE_KEEP_STRINGS
,
50 /* ------------------------------------------------------------------------- */
54 } GnmSearchReplaceValueResult
;
56 gnm_search_replace_value (GnmSearchReplace
*sr
,
58 GnmSearchReplaceValueResult
*res
);
60 /* ------------------------------------------------------------------------- */
63 gnm_search_normalize (const char *txt
)
65 return g_utf8_normalize (txt
, -1, G_NORMALIZE_NFD
);
69 gnm_search_normalize_result (const char *txt
)
71 return g_utf8_normalize (txt
, -1, G_NORMALIZE_NFC
);
74 /* ------------------------------------------------------------------------- */
77 check_number (GnmSearchReplace
*sr
)
79 GODateConventions
const *date_conv
= sheet_date_conv (sr
->sheet
);
80 GOSearchReplace
*gosr
= (GOSearchReplace
*)sr
;
81 GnmValue
*v
= format_match_number (gosr
->search_text
, NULL
, date_conv
);
84 gnm_float f
= value_get_as_float (v
);
86 sr
->low_number
= gnm_add_epsilon (f
);
87 sr
->high_number
= gnm_sub_epsilon (f
);
89 sr
->low_number
= gnm_sub_epsilon (f
);
90 sr
->high_number
= gnm_add_epsilon (f
);
95 sr
->low_number
= sr
->high_number
= gnm_nan
;
101 gnm_search_match_value (GnmSearchReplace
const *sr
, GnmValue
const *val
)
104 if (!VALUE_IS_NUMBER (val
))
106 f
= value_get_as_float (val
);
108 return (sr
->low_number
<= f
&& f
<= sr
->high_number
);
113 gnm_search_replace_verify (GnmSearchReplace
*sr
, gboolean repl
)
115 GError
*error
= NULL
;
116 GOSearchReplace
*gosr
= (GOSearchReplace
*)sr
;
117 g_return_val_if_fail (sr
!= NULL
, NULL
);
119 if (!go_search_replace_verify (gosr
, repl
, &error
)) {
120 char *msg
= g_strdup (error
->message
);
121 g_error_free (error
);
125 if (sr
->is_number
&& gosr
->is_regexp
)
126 return g_strdup (_("Searching for regular expressions and numbers are mutually exclusive."));
129 if (!check_number (sr
))
130 return g_strdup (_("The search text must be a number."));
133 if (sr
->scope
== GNM_SRS_RANGE
) {
136 if (!sr
->range_text
|| sr
->range_text
[0] == 0)
137 return g_strdup (_("You must specify a range to search."));
139 if ((range_list
= global_range_list_parse (sr
->sheet
, sr
->range_text
))
141 return g_strdup (_("The search range is invalid."));
142 range_list_destroy (range_list
);
148 /* ------------------------------------------------------------------------- */
151 cb_order_sheet_row_col (void const *_a
, void const *_b
)
153 GnmEvalPos
const *a
= *(GnmEvalPos
const **)_a
;
154 GnmEvalPos
const *b
= *(GnmEvalPos
const **)_b
;
157 i
= strcmp (a
->sheet
->name_unquoted_collate_key
,
158 b
->sheet
->name_unquoted_collate_key
);
161 if (!i
) i
= (a
->eval
.row
- b
->eval
.row
);
163 /* By column number. */
164 if (!i
) i
= (a
->eval
.col
- b
->eval
.col
);
170 cb_order_sheet_col_row (void const *_a
, void const *_b
)
172 GnmEvalPos
const *a
= *(GnmEvalPos
const **)_a
;
173 GnmEvalPos
const *b
= *(GnmEvalPos
const **)_b
;
176 i
= strcmp (a
->sheet
->name_unquoted_collate_key
,
177 b
->sheet
->name_unquoted_collate_key
);
179 /* By column number. */
180 if (!i
) i
= (a
->eval
.col
- b
->eval
.col
);
183 if (!i
) i
= (a
->eval
.row
- b
->eval
.row
);
189 search_collect_cells_cb (GnmCellIter
const *iter
, gpointer user
)
191 GPtrArray
*cells
= user
;
192 GnmEvalPos
*ep
= g_new (GnmEvalPos
, 1);
194 g_ptr_array_add (cells
, eval_pos_init_cell (ep
, iter
->cell
));
200 * gnm_search_collect_cells:
201 * @sr: #GnmSearchReplace
203 * Collect a list of all cells subject to search.
204 * Returns: (element-type GnmEvalPos) (transfer full): the newly created array.
207 gnm_search_collect_cells (GnmSearchReplace
*sr
)
212 case GNM_SRS_WORKBOOK
:
213 g_return_val_if_fail (sr
->sheet
!= NULL
, NULL
);
214 cells
= workbook_cells (sr
->sheet
->workbook
, TRUE
,
215 GNM_SHEET_VISIBILITY_HIDDEN
);
219 cells
= sheet_cell_positions (sr
->sheet
, TRUE
);
226 cells
= g_ptr_array_new ();
227 range_list
= global_range_list_parse (sr
->sheet
, sr
->range_text
);
228 global_range_list_foreach (range_list
,
229 eval_pos_init_sheet (&ep
, sr
->sheet
),
230 CELL_ITER_IGNORE_BLANK
,
231 search_collect_cells_cb
, cells
);
232 range_list_destroy (range_list
);
238 g_assert_not_reached ();
241 /* Sort our cells. */
242 g_ptr_array_sort (cells
,
243 sr
->by_row
? cb_order_sheet_row_col
: cb_order_sheet_col_row
);
249 * gnm_search_collect_cells_free:
250 * @cells: (element-type GnmEvalPos) (transfer full):
253 gnm_search_collect_cells_free (GPtrArray
*cells
)
257 for (i
= 0; i
< cells
->len
; i
++)
258 g_free (g_ptr_array_index (cells
, i
));
259 g_ptr_array_free (cells
, TRUE
);
262 /* ------------------------------------------------------------------------- */
264 * gnm_search_filter_matching:
265 * @sr: The search spec.
266 * @cells: (element-type GnmEvalPos): Cell positions to filter, presumably a result of gnm_search_collect_cells.
268 * Returns: (element-type GnmSearchFilterResult) (transfer full): matches
272 gnm_search_filter_matching (GnmSearchReplace
*sr
, const GPtrArray
*cells
)
275 GPtrArray
*result
= g_ptr_array_new ();
280 for (i
= 0; i
< cells
->len
; i
++) {
281 GnmSearchReplaceCellResult cell_res
;
282 GnmSearchReplaceValueResult value_res
;
283 GnmSearchReplaceCommentResult comment_res
;
285 const GnmEvalPos
*ep
= g_ptr_array_index (cells
, i
);
287 found
= gnm_search_replace_cell (sr
, ep
, FALSE
, &cell_res
);
288 g_free (cell_res
.old_text
);
289 if (cell_res
.cell
!= NULL
&& found
!= sr
->invert
) {
290 GnmSearchFilterResult
*item
= g_new (GnmSearchFilterResult
, 1);
292 item
->locus
= GNM_SRL_CONTENTS
;
293 g_ptr_array_add (result
, item
);
296 found
= gnm_search_replace_value (sr
, ep
, &value_res
);
297 if (value_res
.cell
!= NULL
&& gnm_cell_has_expr (value_res
.cell
) && found
!= sr
->invert
) {
298 GnmSearchFilterResult
*item
= g_new (GnmSearchFilterResult
, 1);
300 item
->locus
= GNM_SRL_VALUE
;
301 g_ptr_array_add (result
, item
);
304 found
= gnm_search_replace_comment (sr
, ep
, FALSE
, &comment_res
);
305 if (comment_res
.comment
!= NULL
&& found
!= sr
->invert
) {
306 GnmSearchFilterResult
*item
= g_new (GnmSearchFilterResult
, 1);
308 item
->locus
= GNM_SRL_COMMENT
;
309 g_ptr_array_add (result
, item
);
317 * gnm_search_filter_matching_free:
318 * @matches: (element-type GnmSearchFilterResult) (transfer full): matches
321 gnm_search_filter_matching_free (GPtrArray
*matches
)
324 for (i
= 0; i
< matches
->len
; i
++)
325 g_free (g_ptr_array_index (matches
, i
));
326 g_ptr_array_free (matches
, TRUE
);
329 /* ------------------------------------------------------------------------- */
332 gnm_search_replace_comment (GnmSearchReplace
*sr
,
333 const GnmEvalPos
*ep
,
335 GnmSearchReplaceCommentResult
*res
)
340 g_return_val_if_fail (res
, FALSE
);
343 res
->old_text
= NULL
;
344 res
->new_text
= NULL
;
346 g_return_val_if_fail (sr
, FALSE
);
348 if (!sr
->search_comments
) return FALSE
;
349 if (sr
->is_number
) return FALSE
;
351 res
->comment
= sheet_get_comment (ep
->sheet
, &ep
->eval
);
352 if (!res
->comment
) return FALSE
;
354 res
->old_text
= cell_comment_text_get (res
->comment
);
356 norm_text
= gnm_search_normalize (res
->old_text
);
359 res
->new_text
= go_search_replace_string (GO_SEARCH_REPLACE (sr
),
361 found
= (res
->new_text
!= NULL
);
363 char *norm
= gnm_search_normalize_result (res
->new_text
);
364 g_free (res
->new_text
);
365 res
->new_text
= norm
;
368 found
= go_search_match_string (GO_SEARCH_REPLACE (sr
),
376 /* ------------------------------------------------------------------------- */
379 gnm_search_replace_cell (GnmSearchReplace
*sr
,
380 const GnmEvalPos
*ep
,
382 GnmSearchReplaceCellResult
*res
)
386 gboolean is_expr
, is_value
, is_string
, is_other
;
387 gboolean found
= FALSE
;
389 g_return_val_if_fail (res
, FALSE
);
392 res
->old_text
= NULL
;
393 res
->new_text
= NULL
;
395 g_return_val_if_fail (sr
, FALSE
);
397 cell
= res
->cell
= sheet_cell_get (ep
->sheet
, ep
->eval
.col
, ep
->eval
.row
);
398 if (!cell
) return FALSE
;
402 is_expr
= gnm_cell_has_expr (cell
);
403 is_value
= !is_expr
&& !gnm_cell_is_empty (cell
) && v
;
404 is_string
= is_value
&& (VALUE_IS_STRING (v
));
405 is_other
= is_value
&& !is_string
;
408 if (!is_value
|| !VALUE_IS_NUMBER (v
))
410 return gnm_search_match_value (sr
, v
);
413 if ((is_expr
&& sr
->search_expressions
) ||
414 (is_string
&& sr
->search_strings
) ||
415 (is_other
&& sr
->search_other_values
)) {
417 gboolean initial_quote
;
419 res
->old_text
= gnm_cell_get_entered_text (cell
);
420 initial_quote
= (is_string
&& res
->old_text
[0] == '\'');
422 actual_src
= gnm_search_normalize (res
->old_text
+ initial_quote
);
425 res
->new_text
= go_search_replace_string (GO_SEARCH_REPLACE (sr
),
428 char *norm
= gnm_search_normalize_result (res
->new_text
);
429 g_free (res
->new_text
);
430 res
->new_text
= norm
;
432 if (sr
->replace_keep_strings
&& is_string
) {
434 * The initial quote was not part of the s-a-r,
435 * so tack it back on.
437 char *tmp
= g_new (char, strlen (res
->new_text
) + 2);
439 strcpy (tmp
+ 1, res
->new_text
);
440 g_free (res
->new_text
);
446 found
= go_search_match_string (GO_SEARCH_REPLACE (sr
), actual_src
);
454 /* ------------------------------------------------------------------------- */
457 gnm_search_replace_value (GnmSearchReplace
*sr
,
458 const GnmEvalPos
*ep
,
459 GnmSearchReplaceValueResult
*res
)
463 g_return_val_if_fail (res
, FALSE
);
467 g_return_val_if_fail (sr
, FALSE
);
469 if (!sr
->search_expression_results
)
472 cell
= res
->cell
= sheet_cell_get (ep
->sheet
, ep
->eval
.col
, ep
->eval
.row
);
473 if (!cell
|| !gnm_cell_has_expr (cell
) || !cell
->value
)
475 else if (sr
->is_number
) {
476 return gnm_search_match_value (sr
, cell
->value
);
478 char *val
= gnm_search_normalize (value_peek_string (cell
->value
));
479 gboolean res
= go_search_match_string (GO_SEARCH_REPLACE (sr
), val
);
485 /* ------------------------------------------------------------------------- */
488 gnm_search_replace_query_fail (GnmSearchReplace
*sr
,
489 const GnmSearchReplaceCellResult
*res
)
494 sr
->query_func (GNM_SRQ_FAIL
, sr
,
495 res
->cell
, res
->old_text
, res
->new_text
);
499 gnm_search_replace_query_cell (GnmSearchReplace
*sr
,
500 const GnmSearchReplaceCellResult
*res
)
502 if (!sr
->query
|| !sr
->query_func
)
503 return GTK_RESPONSE_YES
;
505 return sr
->query_func (GNM_SRQ_QUERY
, sr
,
506 res
->cell
, res
->old_text
, res
->new_text
);
511 gnm_search_replace_query_comment (GnmSearchReplace
*sr
,
512 const GnmEvalPos
*ep
,
513 const GnmSearchReplaceCommentResult
*res
)
515 if (!sr
->query
|| !sr
->query_func
)
516 return GTK_RESPONSE_YES
;
518 return sr
->query_func (GNM_SRQ_QUERY_COMMENT
, sr
,
519 ep
->sheet
, &ep
->eval
,
520 res
->old_text
, res
->new_text
);
523 /* ------------------------------------------------------------------------- */
526 gnm_search_replace_scope_get_type (void)
528 static GType etype
= 0;
530 static const GEnumValue values
[] = {
531 { GNM_SRS_WORKBOOK
, "GNM_SRS_WORKBOOK", "workbook" },
532 { GNM_SRS_SHEET
, "GNM_SRS_SHEET", "sheet" },
533 { GNM_SRS_RANGE
, "GNM_SRS_RANGE", "range" },
536 etype
= g_enum_register_static ("GnmSearchReplaceScope", values
);
541 /* ------------------------------------------------------------------------- */
544 gnm_search_replace_init (GObject
*obj
)
548 /* ------------------------------------------------------------------------- */
551 gnm_search_replace_get_property (GObject
*object
,
556 GnmSearchReplace
*sr
= (GnmSearchReplace
*)object
;
558 switch (property_id
) {
560 g_value_set_boolean (value
, sr
->is_number
);
562 case PROP_SEARCH_STRINGS
:
563 g_value_set_boolean (value
, sr
->search_strings
);
565 case PROP_SEARCH_OTHER_VALUES
:
566 g_value_set_boolean (value
, sr
->search_other_values
);
568 case PROP_SEARCH_EXPRESSIONS
:
569 g_value_set_boolean (value
, sr
->search_expressions
);
571 case PROP_SEARCH_EXPRESSION_RESULTS
:
572 g_value_set_boolean (value
, sr
->search_expression_results
);
574 case PROP_SEARCH_COMMENTS
:
575 g_value_set_boolean (value
, sr
->search_comments
);
577 case PROP_SEARCH_SCRIPTS
:
578 g_value_set_boolean (value
, sr
->search_scripts
);
581 g_value_set_boolean (value
, sr
->invert
);
584 g_value_set_boolean (value
, sr
->by_row
);
587 g_value_set_boolean (value
, sr
->query
);
589 case PROP_REPLACE_KEEP_STRINGS
:
590 g_value_set_boolean (value
, sr
->replace_keep_strings
);
593 g_value_set_object (value
, sr
->sheet
);
596 g_value_set_enum (value
, sr
->scope
);
598 case PROP_RANGE_TEXT
:
599 g_value_set_string (value
, sr
->range_text
);
602 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
607 /* ------------------------------------------------------------------------- */
610 gnm_search_replace_set_sheet (GnmSearchReplace
*sr
, Sheet
*sheet
)
613 g_object_ref (sheet
);
615 g_object_unref (sr
->sheet
);
620 gnm_search_replace_set_range_text (GnmSearchReplace
*sr
, char const *text
)
622 char *text_copy
= g_strdup (text
);
623 g_free (sr
->range_text
);
624 sr
->range_text
= text_copy
;
628 gnm_search_replace_set_property (GObject
*object
,
633 GnmSearchReplace
*sr
= (GnmSearchReplace
*)object
;
635 switch (property_id
) {
637 sr
->is_number
= g_value_get_boolean (value
);
639 case PROP_SEARCH_STRINGS
:
640 sr
->search_strings
= g_value_get_boolean (value
);
642 case PROP_SEARCH_OTHER_VALUES
:
643 sr
->search_other_values
= g_value_get_boolean (value
);
645 case PROP_SEARCH_EXPRESSIONS
:
646 sr
->search_expressions
= g_value_get_boolean (value
);
648 case PROP_SEARCH_EXPRESSION_RESULTS
:
649 sr
->search_expression_results
= g_value_get_boolean (value
);
651 case PROP_SEARCH_COMMENTS
:
652 sr
->search_comments
= g_value_get_boolean (value
);
654 case PROP_SEARCH_SCRIPTS
:
655 sr
->search_scripts
= g_value_get_boolean (value
);
658 sr
->invert
= g_value_get_boolean (value
);
661 sr
->by_row
= g_value_get_boolean (value
);
664 sr
->query
= g_value_get_boolean (value
);
666 case PROP_REPLACE_KEEP_STRINGS
:
667 sr
->replace_keep_strings
= g_value_get_boolean (value
);
670 gnm_search_replace_set_sheet (sr
, g_value_get_object (value
));
673 sr
->scope
= g_value_get_enum (value
);
675 case PROP_RANGE_TEXT
:
676 gnm_search_replace_set_range_text (sr
, g_value_get_string (value
));
679 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
684 /* ------------------------------------------------------------------------- */
687 gnm_search_replace_finalize (GObject
*obj
)
689 GnmSearchReplace
*sr
= (GnmSearchReplace
*)obj
;
691 gnm_search_replace_set_sheet (sr
, NULL
);
692 g_free (sr
->range_text
);
694 G_OBJECT_CLASS (parent_class
)->finalize (obj
);
697 /* ------------------------------------------------------------------------- */
700 gnm_search_replace_class_init (GObjectClass
*gobject_class
)
702 parent_class
= g_type_class_peek_parent (gobject_class
);
704 gobject_class
->finalize
= gnm_search_replace_finalize
;
705 gobject_class
->get_property
= gnm_search_replace_get_property
;
706 gobject_class
->set_property
= gnm_search_replace_set_property
;
708 g_object_class_install_property
711 g_param_spec_boolean ("is-number",
713 P_("Search for Specific Number Regardless of Formatting?"),
717 g_object_class_install_property
720 g_param_spec_boolean ("search-strings",
721 P_("Search Strings"),
722 P_("Should strings be searched?"),
726 g_object_class_install_property
728 PROP_SEARCH_OTHER_VALUES
,
729 g_param_spec_boolean ("search-other-values",
730 P_("Search Other Values"),
731 P_("Should non-strings be searched?"),
735 g_object_class_install_property
737 PROP_SEARCH_EXPRESSIONS
,
738 g_param_spec_boolean ("search-expressions",
739 P_("Search Expressions"),
740 P_("Should expressions be searched?"),
744 g_object_class_install_property
746 PROP_SEARCH_EXPRESSION_RESULTS
,
747 g_param_spec_boolean ("search-expression-results",
748 P_("Search Expression Results"),
749 P_("Should the results of expressions be searched?"),
753 g_object_class_install_property
755 PROP_SEARCH_COMMENTS
,
756 g_param_spec_boolean ("search-comments",
757 P_("Search Comments"),
758 P_("Should cell comments be searched?"),
762 g_object_class_install_property
765 g_param_spec_boolean ("search-scripts",
766 P_("Search Scripts"),
767 P_("Should scrips (workbook, and worksheet) be searched?"),
771 g_object_class_install_property
774 g_param_spec_boolean ("invert",
776 P_("Collect non-matching items"),
780 g_object_class_install_property
783 g_param_spec_boolean ("by-row",
785 P_("Is the search order by row?"),
789 g_object_class_install_property
792 g_param_spec_boolean ("query",
794 P_("Should we query for each replacement?"),
798 g_object_class_install_property
800 PROP_REPLACE_KEEP_STRINGS
,
801 g_param_spec_boolean ("replace-keep-strings",
803 P_("Should replacement keep strings as strings?"),
807 g_object_class_install_property
810 g_param_spec_object ("sheet",
812 P_("The sheet in which to search."),
816 g_object_class_install_property
819 g_param_spec_enum ("scope",
821 P_("Where to search."),
822 GNM_SEARCH_REPLACE_SCOPE_TYPE
,
826 g_object_class_install_property
829 g_param_spec_string ("range-text",
831 P_("The range in which to search."),
837 /* ------------------------------------------------------------------------- */
839 GSF_CLASS (GnmSearchReplace
, gnm_search_replace
,
840 gnm_search_replace_class_init
, gnm_search_replace_init
, GO_TYPE_SEARCH_REPLACE
)