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
=
80 workbook_date_conv (sr
->sheet
->workbook
);
81 GOSearchReplace
*gosr
= (GOSearchReplace
*)sr
;
82 GnmValue
*v
= format_match_number (gosr
->search_text
, NULL
, date_conv
);
85 gnm_float f
= value_get_as_float (v
);
87 sr
->low_number
= gnm_add_epsilon (f
);
88 sr
->high_number
= gnm_sub_epsilon (f
);
90 sr
->low_number
= gnm_sub_epsilon (f
);
91 sr
->high_number
= gnm_add_epsilon (f
);
96 sr
->low_number
= sr
->high_number
= gnm_nan
;
102 gnm_search_match_value (GnmSearchReplace
const *sr
, GnmValue
const *val
)
105 if (!VALUE_IS_NUMBER (val
))
107 f
= value_get_as_float (val
);
109 return (sr
->low_number
<= f
&& f
<= sr
->high_number
);
114 gnm_search_replace_verify (GnmSearchReplace
*sr
, gboolean repl
)
116 GError
*error
= NULL
;
117 GOSearchReplace
*gosr
= (GOSearchReplace
*)sr
;
118 g_return_val_if_fail (sr
!= NULL
, NULL
);
120 if (!go_search_replace_verify (gosr
, repl
, &error
)) {
121 char *msg
= g_strdup (error
->message
);
122 g_error_free (error
);
126 if (sr
->is_number
&& gosr
->is_regexp
)
127 return g_strdup (_("Searching for regular expressions and numbers are mutually exclusive."));
130 if (!check_number (sr
))
131 return g_strdup (_("The search text must be a number."));
134 if (sr
->scope
== GNM_SRS_RANGE
) {
137 if (!sr
->range_text
|| sr
->range_text
[0] == 0)
138 return g_strdup (_("You must specify a range to search."));
140 if ((range_list
= global_range_list_parse (sr
->sheet
, sr
->range_text
))
142 return g_strdup (_("The search range is invalid."));
143 range_list_destroy (range_list
);
149 /* ------------------------------------------------------------------------- */
152 cb_order_sheet_row_col (void const *_a
, void const *_b
)
154 GnmEvalPos
const *a
= *(GnmEvalPos
const **)_a
;
155 GnmEvalPos
const *b
= *(GnmEvalPos
const **)_b
;
158 i
= strcmp (a
->sheet
->name_unquoted_collate_key
,
159 b
->sheet
->name_unquoted_collate_key
);
162 if (!i
) i
= (a
->eval
.row
- b
->eval
.row
);
164 /* By column number. */
165 if (!i
) i
= (a
->eval
.col
- b
->eval
.col
);
171 cb_order_sheet_col_row (void const *_a
, void const *_b
)
173 GnmEvalPos
const *a
= *(GnmEvalPos
const **)_a
;
174 GnmEvalPos
const *b
= *(GnmEvalPos
const **)_b
;
177 i
= strcmp (a
->sheet
->name_unquoted_collate_key
,
178 b
->sheet
->name_unquoted_collate_key
);
180 /* By column number. */
181 if (!i
) i
= (a
->eval
.col
- b
->eval
.col
);
184 if (!i
) i
= (a
->eval
.row
- b
->eval
.row
);
190 search_collect_cells_cb (GnmCellIter
const *iter
, gpointer user
)
192 GPtrArray
*cells
= user
;
193 GnmEvalPos
*ep
= g_new (GnmEvalPos
, 1);
195 g_ptr_array_add (cells
, eval_pos_init_cell (ep
, iter
->cell
));
201 * gnm_search_collect_cells:
202 * @sr: #GnmSearchReplace
204 * Collect a list of all cells subject to search.
205 * Returns: (element-type GnmEvalPos) (transfer full): the newly created array.
208 gnm_search_collect_cells (GnmSearchReplace
*sr
)
213 case GNM_SRS_WORKBOOK
:
214 g_return_val_if_fail (sr
->sheet
!= NULL
, NULL
);
215 cells
= workbook_cells (sr
->sheet
->workbook
, TRUE
,
216 GNM_SHEET_VISIBILITY_HIDDEN
);
220 cells
= sheet_cell_positions (sr
->sheet
, TRUE
);
227 cells
= g_ptr_array_new ();
228 range_list
= global_range_list_parse (sr
->sheet
, sr
->range_text
);
229 global_range_list_foreach (range_list
,
230 eval_pos_init_sheet (&ep
, sr
->sheet
),
231 CELL_ITER_IGNORE_BLANK
,
232 search_collect_cells_cb
, cells
);
233 range_list_destroy (range_list
);
239 g_assert_not_reached ();
242 /* Sort our cells. */
243 g_ptr_array_sort (cells
,
244 sr
->by_row
? cb_order_sheet_row_col
: cb_order_sheet_col_row
);
250 * gnm_search_collect_cells_free:
251 * @cells: (element-type GnmEvalPos) (transfer full):
254 gnm_search_collect_cells_free (GPtrArray
*cells
)
258 for (i
= 0; i
< cells
->len
; i
++)
259 g_free (g_ptr_array_index (cells
, i
));
260 g_ptr_array_free (cells
, TRUE
);
263 /* ------------------------------------------------------------------------- */
265 * gnm_search_filter_matching:
266 * @sr: The search spec.
267 * @cells: (element-type GnmEvalPos): Cell positions to filter, presumably a result of gnm_search_collect_cells.
269 * Returns: (element-type GnmSearchFilterResult) (transfer full): matches
273 gnm_search_filter_matching (GnmSearchReplace
*sr
, const GPtrArray
*cells
)
276 GPtrArray
*result
= g_ptr_array_new ();
281 for (i
= 0; i
< cells
->len
; i
++) {
282 GnmSearchReplaceCellResult cell_res
;
283 GnmSearchReplaceValueResult value_res
;
284 GnmSearchReplaceCommentResult comment_res
;
286 const GnmEvalPos
*ep
= g_ptr_array_index (cells
, i
);
288 found
= gnm_search_replace_cell (sr
, ep
, FALSE
, &cell_res
);
289 g_free (cell_res
.old_text
);
290 if (cell_res
.cell
!= NULL
&& found
!= sr
->invert
) {
291 GnmSearchFilterResult
*item
= g_new (GnmSearchFilterResult
, 1);
293 item
->locus
= GNM_SRL_CONTENTS
;
294 g_ptr_array_add (result
, item
);
297 found
= gnm_search_replace_value (sr
, ep
, &value_res
);
298 if (value_res
.cell
!= NULL
&& gnm_cell_has_expr (value_res
.cell
) && found
!= sr
->invert
) {
299 GnmSearchFilterResult
*item
= g_new (GnmSearchFilterResult
, 1);
301 item
->locus
= GNM_SRL_VALUE
;
302 g_ptr_array_add (result
, item
);
305 found
= gnm_search_replace_comment (sr
, ep
, FALSE
, &comment_res
);
306 if (comment_res
.comment
!= NULL
&& found
!= sr
->invert
) {
307 GnmSearchFilterResult
*item
= g_new (GnmSearchFilterResult
, 1);
309 item
->locus
= GNM_SRL_COMMENT
;
310 g_ptr_array_add (result
, item
);
318 * gnm_search_filter_matching_free:
319 * @matches: (element-type GnmSearchFilterResult) (transfer full): matches
322 gnm_search_filter_matching_free (GPtrArray
*matches
)
325 for (i
= 0; i
< matches
->len
; i
++)
326 g_free (g_ptr_array_index (matches
, i
));
327 g_ptr_array_free (matches
, TRUE
);
330 /* ------------------------------------------------------------------------- */
333 gnm_search_replace_comment (GnmSearchReplace
*sr
,
334 const GnmEvalPos
*ep
,
336 GnmSearchReplaceCommentResult
*res
)
341 g_return_val_if_fail (res
, FALSE
);
344 res
->old_text
= NULL
;
345 res
->new_text
= NULL
;
347 g_return_val_if_fail (sr
, FALSE
);
349 if (!sr
->search_comments
) return FALSE
;
350 if (sr
->is_number
) return FALSE
;
352 res
->comment
= sheet_get_comment (ep
->sheet
, &ep
->eval
);
353 if (!res
->comment
) return FALSE
;
355 res
->old_text
= cell_comment_text_get (res
->comment
);
357 norm_text
= gnm_search_normalize (res
->old_text
);
360 res
->new_text
= go_search_replace_string (GO_SEARCH_REPLACE (sr
),
362 found
= (res
->new_text
!= NULL
);
364 char *norm
= gnm_search_normalize_result (res
->new_text
);
365 g_free (res
->new_text
);
366 res
->new_text
= norm
;
369 found
= go_search_match_string (GO_SEARCH_REPLACE (sr
),
377 /* ------------------------------------------------------------------------- */
380 gnm_search_replace_cell (GnmSearchReplace
*sr
,
381 const GnmEvalPos
*ep
,
383 GnmSearchReplaceCellResult
*res
)
387 gboolean is_expr
, is_value
, is_string
, is_other
;
388 gboolean found
= FALSE
;
390 g_return_val_if_fail (res
, FALSE
);
393 res
->old_text
= NULL
;
394 res
->new_text
= NULL
;
396 g_return_val_if_fail (sr
, FALSE
);
398 cell
= res
->cell
= sheet_cell_get (ep
->sheet
, ep
->eval
.col
, ep
->eval
.row
);
399 if (!cell
) return FALSE
;
403 is_expr
= gnm_cell_has_expr (cell
);
404 is_value
= !is_expr
&& !gnm_cell_is_empty (cell
) && v
;
405 is_string
= is_value
&& (VALUE_IS_STRING (v
));
406 is_other
= is_value
&& !is_string
;
409 if (!is_value
|| !VALUE_IS_NUMBER (v
))
411 return gnm_search_match_value (sr
, v
);
414 if ((is_expr
&& sr
->search_expressions
) ||
415 (is_string
&& sr
->search_strings
) ||
416 (is_other
&& sr
->search_other_values
)) {
418 gboolean initial_quote
;
420 res
->old_text
= gnm_cell_get_entered_text (cell
);
421 initial_quote
= (is_string
&& res
->old_text
[0] == '\'');
423 actual_src
= gnm_search_normalize (res
->old_text
+ initial_quote
);
426 res
->new_text
= go_search_replace_string (GO_SEARCH_REPLACE (sr
),
429 char *norm
= gnm_search_normalize_result (res
->new_text
);
430 g_free (res
->new_text
);
431 res
->new_text
= norm
;
433 if (sr
->replace_keep_strings
&& is_string
) {
435 * The initial quote was not part of the s-a-r,
436 * so tack it back on.
438 char *tmp
= g_new (char, strlen (res
->new_text
) + 2);
440 strcpy (tmp
+ 1, res
->new_text
);
441 g_free (res
->new_text
);
447 found
= go_search_match_string (GO_SEARCH_REPLACE (sr
), actual_src
);
455 /* ------------------------------------------------------------------------- */
458 gnm_search_replace_value (GnmSearchReplace
*sr
,
459 const GnmEvalPos
*ep
,
460 GnmSearchReplaceValueResult
*res
)
464 g_return_val_if_fail (res
, FALSE
);
468 g_return_val_if_fail (sr
, FALSE
);
470 if (!sr
->search_expression_results
)
473 cell
= res
->cell
= sheet_cell_get (ep
->sheet
, ep
->eval
.col
, ep
->eval
.row
);
474 if (!cell
|| !gnm_cell_has_expr (cell
) || !cell
->value
)
476 else if (sr
->is_number
) {
477 return gnm_search_match_value (sr
, cell
->value
);
479 char *val
= gnm_search_normalize (value_peek_string (cell
->value
));
480 gboolean res
= go_search_match_string (GO_SEARCH_REPLACE (sr
), val
);
486 /* ------------------------------------------------------------------------- */
489 gnm_search_replace_query_fail (GnmSearchReplace
*sr
,
490 const GnmSearchReplaceCellResult
*res
)
495 sr
->query_func (GNM_SRQ_FAIL
, sr
,
496 res
->cell
, res
->old_text
, res
->new_text
);
500 gnm_search_replace_query_cell (GnmSearchReplace
*sr
,
501 const GnmSearchReplaceCellResult
*res
)
503 if (!sr
->query
|| !sr
->query_func
)
504 return GTK_RESPONSE_YES
;
506 return sr
->query_func (GNM_SRQ_QUERY
, sr
,
507 res
->cell
, res
->old_text
, res
->new_text
);
512 gnm_search_replace_query_comment (GnmSearchReplace
*sr
,
513 const GnmEvalPos
*ep
,
514 const GnmSearchReplaceCommentResult
*res
)
516 if (!sr
->query
|| !sr
->query_func
)
517 return GTK_RESPONSE_YES
;
519 return sr
->query_func (GNM_SRQ_QUERY_COMMENT
, sr
,
520 ep
->sheet
, &ep
->eval
,
521 res
->old_text
, res
->new_text
);
524 /* ------------------------------------------------------------------------- */
527 gnm_search_replace_scope_get_type (void)
529 static GType etype
= 0;
531 static const GEnumValue values
[] = {
532 { GNM_SRS_WORKBOOK
, "GNM_SRS_WORKBOOK", "workbook" },
533 { GNM_SRS_SHEET
, "GNM_SRS_SHEET", "sheet" },
534 { GNM_SRS_RANGE
, "GNM_SRS_RANGE", "range" },
537 etype
= g_enum_register_static ("GnmSearchReplaceScope", values
);
542 /* ------------------------------------------------------------------------- */
545 gnm_search_replace_init (GObject
*obj
)
549 /* ------------------------------------------------------------------------- */
552 gnm_search_replace_get_property (GObject
*object
,
557 GnmSearchReplace
*sr
= (GnmSearchReplace
*)object
;
559 switch (property_id
) {
561 g_value_set_boolean (value
, sr
->is_number
);
563 case PROP_SEARCH_STRINGS
:
564 g_value_set_boolean (value
, sr
->search_strings
);
566 case PROP_SEARCH_OTHER_VALUES
:
567 g_value_set_boolean (value
, sr
->search_other_values
);
569 case PROP_SEARCH_EXPRESSIONS
:
570 g_value_set_boolean (value
, sr
->search_expressions
);
572 case PROP_SEARCH_EXPRESSION_RESULTS
:
573 g_value_set_boolean (value
, sr
->search_expression_results
);
575 case PROP_SEARCH_COMMENTS
:
576 g_value_set_boolean (value
, sr
->search_comments
);
578 case PROP_SEARCH_SCRIPTS
:
579 g_value_set_boolean (value
, sr
->search_scripts
);
582 g_value_set_boolean (value
, sr
->invert
);
585 g_value_set_boolean (value
, sr
->by_row
);
588 g_value_set_boolean (value
, sr
->query
);
590 case PROP_REPLACE_KEEP_STRINGS
:
591 g_value_set_boolean (value
, sr
->replace_keep_strings
);
594 g_value_set_object (value
, sr
->sheet
);
597 g_value_set_enum (value
, sr
->scope
);
599 case PROP_RANGE_TEXT
:
600 g_value_set_string (value
, sr
->range_text
);
603 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
608 /* ------------------------------------------------------------------------- */
611 gnm_search_replace_set_sheet (GnmSearchReplace
*sr
, Sheet
*sheet
)
614 g_object_ref (sheet
);
616 g_object_unref (sr
->sheet
);
621 gnm_search_replace_set_range_text (GnmSearchReplace
*sr
, char const *text
)
623 char *text_copy
= g_strdup (text
);
624 g_free (sr
->range_text
);
625 sr
->range_text
= text_copy
;
629 gnm_search_replace_set_property (GObject
*object
,
634 GnmSearchReplace
*sr
= (GnmSearchReplace
*)object
;
636 switch (property_id
) {
638 sr
->is_number
= g_value_get_boolean (value
);
640 case PROP_SEARCH_STRINGS
:
641 sr
->search_strings
= g_value_get_boolean (value
);
643 case PROP_SEARCH_OTHER_VALUES
:
644 sr
->search_other_values
= g_value_get_boolean (value
);
646 case PROP_SEARCH_EXPRESSIONS
:
647 sr
->search_expressions
= g_value_get_boolean (value
);
649 case PROP_SEARCH_EXPRESSION_RESULTS
:
650 sr
->search_expression_results
= g_value_get_boolean (value
);
652 case PROP_SEARCH_COMMENTS
:
653 sr
->search_comments
= g_value_get_boolean (value
);
655 case PROP_SEARCH_SCRIPTS
:
656 sr
->search_scripts
= g_value_get_boolean (value
);
659 sr
->invert
= g_value_get_boolean (value
);
662 sr
->by_row
= g_value_get_boolean (value
);
665 sr
->query
= g_value_get_boolean (value
);
667 case PROP_REPLACE_KEEP_STRINGS
:
668 sr
->replace_keep_strings
= g_value_get_boolean (value
);
671 gnm_search_replace_set_sheet (sr
, g_value_get_object (value
));
674 sr
->scope
= g_value_get_enum (value
);
676 case PROP_RANGE_TEXT
:
677 gnm_search_replace_set_range_text (sr
, g_value_get_string (value
));
680 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
685 /* ------------------------------------------------------------------------- */
688 gnm_search_replace_finalize (GObject
*obj
)
690 GnmSearchReplace
*sr
= (GnmSearchReplace
*)obj
;
692 gnm_search_replace_set_sheet (sr
, NULL
);
693 g_free (sr
->range_text
);
695 G_OBJECT_CLASS (parent_class
)->finalize (obj
);
698 /* ------------------------------------------------------------------------- */
701 gnm_search_replace_class_init (GObjectClass
*gobject_class
)
703 parent_class
= g_type_class_peek_parent (gobject_class
);
705 gobject_class
->finalize
= gnm_search_replace_finalize
;
706 gobject_class
->get_property
= gnm_search_replace_get_property
;
707 gobject_class
->set_property
= gnm_search_replace_set_property
;
709 g_object_class_install_property
712 g_param_spec_boolean ("is-number",
714 P_("Search for Specific Number Regardless of Formatting?"),
718 g_object_class_install_property
721 g_param_spec_boolean ("search-strings",
722 P_("Search Strings"),
723 P_("Should strings be searched?"),
727 g_object_class_install_property
729 PROP_SEARCH_OTHER_VALUES
,
730 g_param_spec_boolean ("search-other-values",
731 P_("Search Other Values"),
732 P_("Should non-strings be searched?"),
736 g_object_class_install_property
738 PROP_SEARCH_EXPRESSIONS
,
739 g_param_spec_boolean ("search-expressions",
740 P_("Search Expressions"),
741 P_("Should expressions be searched?"),
745 g_object_class_install_property
747 PROP_SEARCH_EXPRESSION_RESULTS
,
748 g_param_spec_boolean ("search-expression-results",
749 P_("Search Expression Results"),
750 P_("Should the results of expressions be searched?"),
754 g_object_class_install_property
756 PROP_SEARCH_COMMENTS
,
757 g_param_spec_boolean ("search-comments",
758 P_("Search Comments"),
759 P_("Should cell comments be searched?"),
763 g_object_class_install_property
766 g_param_spec_boolean ("search-scripts",
767 P_("Search Scripts"),
768 P_("Should scrips (workbook, and worksheet) be searched?"),
772 g_object_class_install_property
775 g_param_spec_boolean ("invert",
777 P_("Collect non-matching items"),
781 g_object_class_install_property
784 g_param_spec_boolean ("by-row",
786 P_("Is the search order by row?"),
790 g_object_class_install_property
793 g_param_spec_boolean ("query",
795 P_("Should we query for each replacement?"),
799 g_object_class_install_property
801 PROP_REPLACE_KEEP_STRINGS
,
802 g_param_spec_boolean ("replace-keep-strings",
804 P_("Should replacement keep strings as strings?"),
808 g_object_class_install_property
811 g_param_spec_object ("sheet",
813 P_("The sheet in which to search."),
817 g_object_class_install_property
820 g_param_spec_enum ("scope",
822 P_("Where to search."),
823 GNM_SEARCH_REPLACE_SCOPE_TYPE
,
827 g_object_class_install_property
830 g_param_spec_string ("range-text",
832 P_("The range in which to search."),
838 /* ------------------------------------------------------------------------- */
840 GSF_CLASS (GnmSearchReplace
, gnm_search_replace
,
841 gnm_search_replace_class_init
, gnm_search_replace_init
, GO_TYPE_SEARCH_REPLACE
)