1 /*
2 * regutf8.c: UTF-8 regexp routines.
3 *
4 * Author:
5 * Morten Welinder (terra@gnome.org)
6 *
7 * This library 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 library is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Library General Public License for more details.
16 *
17 * You should have received a copy of the GNU Library General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
20 * USA.
21 */
22
23 #include <goffice/goffice-config.h>
24 #include "regutf8.h"
25 #include "go-glib-extras.h"
26 #include <gsf/gsf-impl-utils.h>
27 #include <glib/gi18n-lib.h>
28 #include <string.h>
29
30 /* ------------------------------------------------------------------------- */
31 /**
32 * GORegexp:
33 * @re_nsub: number of capturing subpatterns.
34 **/
35
36 /**
37 * GORegmatch:
38 * @rm_so: start offset.
39 * @rm_eo: end offset.
40 **/
41
42 void
go_regfree(GORegexp * gor)43 go_regfree (GORegexp *gor)
44 {
45 if (gor->ppcre) {
46 g_regex_unref (gor->ppcre);
47 gor->ppcre = NULL;
48 }
49 }
50
51 size_t
go_regerror(int errcode,const GORegexp * gor,char * dst,size_t dstsize)52 go_regerror (int errcode, const GORegexp *gor, char *dst, size_t dstsize)
53 {
54 const char *err;
55 size_t errlen;
56
57 switch (errcode) {
58 case GO_REG_NOERROR: err = "?"; break;
59 case GO_REG_NOMATCH: err = _("Pattern not found."); break;
60 default:
61 case GO_REG_BADPAT: err = _("Invalid pattern."); break;
62 case GO_REG_ECOLLATE: err = _("Invalid collating element."); break;
63 case GO_REG_ECTYPE: err = _("Invalid character class name."); break;
64 case GO_REG_EESCAPE: err = _("Trailing backslash."); break;
65 case GO_REG_ESUBREG: err = _("Invalid back reference."); break;
66 case GO_REG_EBRACK: err = _("Unmatched left bracket."); break;
67 case GO_REG_EPAREN: err = _("Parenthesis imbalance."); break;
68 case GO_REG_EBRACE: err = _("Unmatched \\{."); break;
69 case GO_REG_BADBR: err = _("Invalid contents of \\{\\}."); break;
70 case GO_REG_ERANGE: err = _("Invalid range end."); break;
71 case GO_REG_ESPACE: err = _("Out of memory."); break;
72 case GO_REG_BADRPT: err = _("Invalid repetition operator."); break;
73 case GO_REG_EEND: err = _("Premature end of pattern."); break;
74 case GO_REG_ESIZE: err = _("Pattern is too big."); break;
75 case GO_REG_ERPAREN: err = _("Unmatched ) or \\)"); break;
76 };
77
78 errlen = strlen (err);
79 if (dstsize > 0) {
80 size_t copylen = MIN (errlen, dstsize - 1);
81 memcpy (dst, err, copylen);
82 dst[copylen] = 0;
83 }
84
85 return errlen;
86 }
87
88 int
go_regcomp(GORegexp * gor,const char * pat,int cflags)89 go_regcomp (GORegexp *gor, const char *pat, int cflags)
90 {
91 GError *error = NULL;
92 GRegex *r;
93 int coptions =
94 ((cflags & GO_REG_ICASE) ? G_REGEX_CASELESS : 0) |
95 ((cflags & GO_REG_NEWLINE) ? G_REGEX_MULTILINE : 0);
96
97 gor->ppcre = r = g_regex_new (pat, coptions, 0, &error);
98
99 if (r == NULL) {
100 /* 10, 19, 22 and 37 are not handled by GRegex. */
101 switch (error->code) {
102 case G_REGEX_ERROR_STRAY_BACKSLASH:
103 case G_REGEX_ERROR_MISSING_CONTROL_CHAR:
104 case G_REGEX_ERROR_UNRECOGNIZED_ESCAPE:
105 /* case 37: */
106 return GO_REG_EESCAPE;
107 case G_REGEX_ERROR_QUANTIFIERS_OUT_OF_ORDER:
108 case G_REGEX_ERROR_QUANTIFIER_TOO_BIG:
109 return GO_REG_EBRACE;
110 case G_REGEX_ERROR_UNTERMINATED_CHARACTER_CLASS:
111 return GO_REG_EBRACK;
112 case G_REGEX_ERROR_INVALID_ESCAPE_IN_CHARACTER_CLASS:
113 case G_REGEX_ERROR_UNKNOWN_POSIX_CLASS_NAME:
114 return GO_REG_ECTYPE;
115 case G_REGEX_ERROR_RANGE_OUT_OF_ORDER:
116 return GO_REG_ERANGE;
117 case G_REGEX_ERROR_NOTHING_TO_REPEAT:
118 /* case 10: */
119 return GO_REG_BADRPT;
120 case G_REGEX_ERROR_UNMATCHED_PARENTHESIS:
121 case G_REGEX_ERROR_UNTERMINATED_COMMENT:
122 /* case 22: */
123 return GO_REG_EPAREN;
124 case G_REGEX_ERROR_INEXISTENT_SUBPATTERN_REFERENCE:
125 return GO_REG_ESUBREG;
126 case G_REGEX_ERROR_EXPRESSION_TOO_LARGE:
127 /* case 19: */
128 return GO_REG_ESIZE;
129 case G_REGEX_ERROR_MEMORY_ERROR:
130 return GO_REG_ESPACE;
131 default:
132 return GO_REG_BADPAT;
133 }
134 } else {
135 gor->re_nsub = g_regex_get_capture_count (r);
136 gor->nosub = (cflags & GO_REG_NOSUB) != 0;
137 return 0;
138 }
139 return 0;
140 }
141
142 int
go_regexec(const GORegexp * gor,const char * txt,size_t nmatch,GORegmatch * pmatch,int eflags)143 go_regexec (const GORegexp *gor, const char *txt,
144 size_t nmatch, GORegmatch *pmatch, int eflags)
145 {
146 int eoptions =
147 ((eflags & GO_REG_NOTBOL) ? G_REGEX_MATCH_NOTBOL : 0) |
148 ((eflags & GO_REG_NOTEOL) ? G_REGEX_MATCH_NOTEOL : 0);
149 size_t i = 0;
150 gboolean matched;
151 GMatchInfo *match_info = NULL;
152
153 /* We need to totally ignore nmatch and pmatch in this case. */
154 if (gor->nosub)
155 nmatch = 0;
156
157 /* Paranoia. */
158 if (nmatch >= G_MAXINT / (2 * sizeof (GORegmatch)))
159 return GO_REG_ESPACE;
160
161 matched = g_regex_match (gor->ppcre, txt, eoptions,
162 nmatch ? &match_info : NULL);
163
164 for (i = 0; matched && i < nmatch; i++) {
165 gint start_pos = -1, end_pos = -1;
166 g_match_info_fetch_pos (match_info, i, &start_pos, &end_pos);
167 pmatch[i].rm_so = start_pos;
168 pmatch[i].rm_eo = end_pos;
169 }
170 if (match_info)
171 g_match_info_free (match_info);
172
173 return matched ? GO_REG_NOERROR : GO_REG_NOMATCH;
174 }
175
176 /* ------------------------------------------------------------------------- */
177
178 static GObjectClass *parent_class;
179
180 enum {
181 PROP_0,
182 PROP_SEARCH_TEXT,
183 PROP_REPLACE_TEXT,
184 PROP_IS_REGEXP,
185 PROP_IGNORE_CASE,
186 PROP_PRESERVE_CASE,
187 PROP_MATCH_WORDS
188 };
189
190 /* ------------------------------------------------------------------------- */
191
192 GQuark
go_search_replace_error_quark(void)193 go_search_replace_error_quark (void)
194 {
195 static GQuark q = 0;
196 if (q == 0)
197 q = g_quark_from_static_string ("go-search-replace-error-quark");
198
199 return q;
200 }
201
202 /* ------------------------------------------------------------------------- */
203
204 static void
kill_compiled(GOSearchReplace * sr)205 kill_compiled (GOSearchReplace *sr)
206 {
207 if (sr->comp_search) {
208 go_regfree (sr->comp_search);
209 g_free (sr->comp_search);
210 sr->comp_search = NULL;
211 }
212 }
213
214 /* ------------------------------------------------------------------------- */
215
216 static int
go_search_replace_compile(GOSearchReplace * sr)217 go_search_replace_compile (GOSearchReplace *sr)
218 {
219 const char *pattern;
220 char *tmp;
221 int flags = 0;
222 int res;
223
224 g_return_val_if_fail (sr && sr->search_text, GO_REG_EEND);
225
226 kill_compiled (sr);
227
228 if (sr->is_regexp) {
229 pattern = sr->search_text;
230 tmp = NULL;
231 sr->plain_replace =
232 (sr->replace_text &&
233 g_utf8_strchr (sr->replace_text, -1, '$') == NULL &&
234 g_utf8_strchr (sr->replace_text, -1, '\\') == NULL);
235 } else {
236 /*
237 * Create a regular expression equivalent to the search
238 * string. (Thus hoping the regular expression search
239 * routines are pretty good.)
240 */
241 GString *regexp = g_string_new (NULL);
242 go_regexp_quote (regexp, sr->search_text);
243 pattern = tmp = g_string_free (regexp, FALSE);
244
245 sr->plain_replace = TRUE;
246 }
247
248 if (sr->ignore_case) flags |= GO_REG_ICASE;
249
250 sr->comp_search = g_new0 (GORegexp, 1);
251 res = go_regcomp (sr->comp_search, pattern, flags);
252
253 g_free (tmp);
254
255 return res;
256 }
257
258 /* ------------------------------------------------------------------------- */
259 /**
260 * go_search_replace_verify:
261 * @sr: Search-and-Replace info to be checked
262 * @repl: Check replacement part too.
263 * @err: Location to store error message.
264 *
265 * Returns: %TRUE if search-and-replace data is valid.
266 **/
267 gboolean
go_search_replace_verify(GOSearchReplace * sr,gboolean repl,GError ** err)268 go_search_replace_verify (GOSearchReplace *sr, gboolean repl, GError **err)
269 {
270 int comp_err;
271 g_return_val_if_fail (sr != NULL, err ? ((*err = NULL), FALSE) : FALSE);
272
273 if (!sr->search_text || sr->search_text[0] == 0) {
274 if (err)
275 g_set_error (err,
276 go_search_replace_error_quark (),
277 0,
278 _("Search string must not be empty."));
279 return FALSE;
280 }
281
282 if (repl && !sr->replace_text) {
283 /* Probably a programmer error. */
284 if (err)
285 g_set_error (err,
286 go_search_replace_error_quark (),
287 0,
288 _("Replacement string must be set."));
289 return FALSE;
290 }
291
292 comp_err = go_search_replace_compile (sr);
293 if (comp_err) {
294 if (err) {
295 char buf[500];
296 go_regerror (comp_err, sr->comp_search, buf, sizeof (buf));
297 g_set_error (err,
298 go_search_replace_error_quark (),
299 0,
300 _("Invalid search pattern (%s)"),
301 buf);
302 }
303 return FALSE;
304 }
305
306 if (repl && !sr->plain_replace) {
307 const char *s;
308
309 for (s = sr->replace_text; *s; s = g_utf8_next_char (s)) {
310 switch (*s) {
311 case '$':
312 s++;
313 switch (*s) {
314 case '1': case '2': case '3': case '4': case '5':
315 case '6': case '7': case '8': case '9':
316 if ((*s - '0') <= (int)sr->comp_search->re_nsub)
317 break;
318 /* Fall through */
319 default:
320 if (err)
321 g_set_error (err,
322 go_search_replace_error_quark (),
323 0,
324 _("Invalid $-specification in replacement."));
325 return FALSE;
326 }
327 break;
328 case '\\':
329 if (s[1] == 0) {
330 if (err)
331 g_set_error (err,
332 go_search_replace_error_quark (),
333 0,
334 _("Invalid trailing backslash in replacement."));
335 return FALSE;
336 }
337 s++;
338 break;
339 }
340 }
341 }
342
343 return TRUE;
344 }
345
346 /* ------------------------------------------------------------------------- */
347 /*
348 * Quote a single UTF-8 encoded character from s into target and return the
349 * location of the next character in s.
350 */
351 const char *
go_regexp_quote1(GString * target,const char * s)352 go_regexp_quote1 (GString *target, const char *s)
353 {
354 g_return_val_if_fail (target != NULL, NULL);
355 g_return_val_if_fail (s != NULL, NULL);
356
357 switch (*s) {
358 case '.': case '[': case '\\':
359 case '*': case '+': case '{': case '?':
360 case '^': case '$':
361 case '(': case '|': case ')':
362 g_string_append_c (target, '\\');
363 g_string_append_c (target, *s);
364 return s + 1;
365
366 case 0:
367 return s;
368
369 default:
370 do {
371 g_string_append_c (target, *s);
372 s++;
373 } while ((*s & 0xc0) == 0x80);
374
375 return s;
376 }
377 }
378
379 /* ------------------------------------------------------------------------- */
380
381 /*
382 * Regexp quote a UTF-8 string.
383 */
384 void
go_regexp_quote(GString * target,const char * s)385 go_regexp_quote (GString *target, const char *s)
386 {
387 g_return_if_fail (target != NULL);
388 g_return_if_fail (s != NULL);
389
390 while (*s)
391 s = go_regexp_quote1 (target, s);
392 }
393
394 /* ------------------------------------------------------------------------- */
395
396 static gboolean
match_is_word(const char * src,const GORegmatch * pm,gboolean bolp)397 match_is_word (const char *src, const GORegmatch *pm, gboolean bolp)
398 {
399 /* The empty string is not a word. */
400 if (pm->rm_so == pm->rm_eo)
401 return FALSE;
402
403 if (pm->rm_so > 0 || !bolp) {
404 /* We get here when something actually preceded the match. */
405 gunichar c_pre = g_utf8_get_char (g_utf8_prev_char (src + pm->rm_so));
406 if (g_unichar_isalnum (c_pre))
407 return FALSE;
408 }
409
410 {
411 gunichar c_post = g_utf8_get_char (src + pm->rm_eo);
412 if (c_post != 0 && g_unichar_isalnum (c_post))
413 return FALSE;
414 }
415
416 return TRUE;
417 }
418
419 /* ------------------------------------------------------------------------- */
420
421 typedef enum {
422 SC_Upper, /* At least one letter. No lower case. */
423 SC_Capital, /* Something Like: This */
424 SC_Other
425 } SearchCase;
426
427 static SearchCase
inspect_case(const char * p,const char * pend)428 inspect_case (const char *p, const char *pend)
429 {
430 gboolean is_upper = TRUE;
431 gboolean is_capital = TRUE;
432 gboolean has_letter = FALSE;
433 gboolean expect_upper = TRUE;
434
435 for (; p < pend; p = g_utf8_next_char (p)) {
436 gunichar c = g_utf8_get_char (p);
437 if (g_unichar_isalpha (c)) {
438 has_letter = TRUE;
439 if (!g_unichar_isupper (c)) {
440 is_upper = FALSE;
441 }
442
443 if (expect_upper ? !g_unichar_isupper (c) : !g_unichar_islower (c)) {
444 is_capital = FALSE;
445 }
446 expect_upper = FALSE;
447 } else
448 expect_upper = TRUE;
449 }
450
451 if (has_letter) {
452 if (is_upper)
453 return SC_Upper;
454 if (is_capital)
455 return SC_Capital;
456 }
457
458 return SC_Other;
459 }
460
461
462 static char *
calculate_replacement(GOSearchReplace * sr,const char * src,const GORegmatch * pm)463 calculate_replacement (GOSearchReplace *sr, const char *src, const GORegmatch *pm)
464 {
465 char *res;
466
467 if (sr->plain_replace) {
468 res = g_strdup (sr->replace_text);
469 } else {
470 const char *s;
471 GString *gres = g_string_sized_new (strlen (sr->replace_text));
472
473 for (s = sr->replace_text; *s; s = g_utf8_next_char (s)) {
474 switch (*s) {
475 case '$':
476 {
477 int n = s[1] - '0';
478 s++;
479
480 g_assert (n > 0 && n <= (int)sr->comp_search->re_nsub);
481 g_string_append_len (gres,
482 src + pm[n].rm_so,
483 pm[n].rm_eo - pm[n].rm_so);
484 break;
485 }
486 case '\\':
487 s++;
488 g_assert (*s != 0);
489 g_string_append_unichar (gres, g_utf8_get_char (s));
490 break;
491 default:
492 g_string_append_unichar (gres, g_utf8_get_char (s));
493 break;
494 }
495 }
496
497 res = gres->str;
498 g_string_free (gres, FALSE);
499 }
500
501 /*
502 * Try to preserve the case during replacement, i.e., do the
503 * following substitutions:
504 *
505 * search -> replace
506 * Search -> Replace
507 * SEARCH -> REPLACE
508 * TheSearch -> TheReplace
509 */
510 if (sr->preserve_case) {
511 SearchCase sc =
512 inspect_case (src + pm->rm_so, src + pm->rm_eo);
513
514 switch (sc) {
515 case SC_Upper:
516 {
517 char *newres = g_utf8_strup (res, -1);
518 g_free (res);
519 res = newres;
520 break;
521 }
522
523 case SC_Capital:
524 {
525 char *newres = go_utf8_strcapital (res, -1);
526 g_free (res);
527 res = newres;
528 break;
529 }
530
531 case SC_Other:
532 break;
533
534 #ifndef DEBUG_SWITCH_ENUM
535 default:
536 g_assert_not_reached ();
537 #endif
538 }
539 }
540
541 return res;
542 }
543
544 /* ------------------------------------------------------------------------- */
545
546 gboolean
go_search_match_string(GOSearchReplace * sr,const char * src)547 go_search_match_string (GOSearchReplace *sr, const char *src)
548 {
549 int flags = 0;
550
551 g_return_val_if_fail (sr, FALSE);
552
553 if (!sr->comp_search) {
554 go_search_replace_compile (sr);
555 g_return_val_if_fail (sr->comp_search, FALSE);
556 }
557
558 while (1) {
559 GORegmatch match;
560 int ret = go_regexec (sr->comp_search, src, 1, &match, flags);
561
562 switch (ret) {
563 case 0:
564 if (!sr->match_words)
565 return TRUE;
566
567 if (match_is_word (src, &match, (flags & GO_REG_NOTBOL) != 0))
568 return TRUE;
569
570 /*
571 * We had a match, but it's not a word. Pretend we saw
572 * a one-character match and continue after that.
573 */
574 flags |= GO_REG_NOTBOL;
575 src = g_utf8_next_char (src + match.rm_so);
576 break;
577
578 case GO_REG_NOMATCH:
579 return FALSE;
580
581 default:
582 g_error ("Unexpected error code from regexec: %d.", ret);
583 return FALSE;
584 }
585 }
586 }
587
588 /* ------------------------------------------------------------------------- */
589
590 /**
591 * go_search_replace_string:
592 * @sr: A #GOSearchReplace search-and-replace specification
593 * @src: the source string
594 *
595 * Returns: (transfer full) (nullable): the string after search-and-replace.
596 * However, if nothing changed, %NULL is returned.
597 */
598 char *
go_search_replace_string(GOSearchReplace * sr,const char * src)599 go_search_replace_string (GOSearchReplace *sr, const char *src)
600 {
601 int nmatch;
602 GORegmatch *pmatch;
603 GString *res = NULL;
604 int ret;
605 int flags = 0;
606
607 g_return_val_if_fail (sr, NULL);
608 g_return_val_if_fail (sr->replace_text, NULL);
609
610 if (!sr->comp_search) {
611 go_search_replace_compile (sr);
612 g_return_val_if_fail (sr->comp_search, NULL);
613 }
614
615 nmatch = 1 + sr->comp_search->re_nsub;
616 pmatch = g_new (GORegmatch, nmatch);
617
618 while ((ret = go_regexec (sr->comp_search, src, nmatch, pmatch, flags)) == 0) {
619 if (!res) {
620 /* The size here is a bit arbitrary. */
621 int size = strlen (src) +
622 10 * strlen (sr->replace_text);
623 res = g_string_sized_new (size);
624 }
625
626 if (pmatch[0].rm_so > 0) {
627 g_string_append_len (res, src, pmatch[0].rm_so);
628 }
629
630 if (sr->match_words && !match_is_word (src, pmatch,
631 (flags & GO_REG_NOTBOL) != 0)) {
632 /* We saw a fake match. */
633 if (pmatch[0].rm_so < pmatch[0].rm_eo) {
634 const char *p = src + pmatch[0].rm_so;
635 gunichar c = g_utf8_get_char (p);
636
637 g_string_append_unichar (res, c);
638
639 /* Pretend we saw a one-character match. */
640 pmatch[0].rm_eo = pmatch[0].rm_so +
641 (g_utf8_next_char (p) - p);
642 }
643 } else {
644 char *replacement =
645 calculate_replacement (sr, src, pmatch);
646 g_string_append (res, replacement);
647 g_free (replacement);
648
649 if (src[pmatch[0].rm_eo] == 0) {
650 /*
651 * We matched and replaced the last character
652 * of the string. Do not continue as we might
653 * then match the empty string at the end and
654 * re-add the replacement. This would happen,
655 * for example, if you searched for ".*".
656 */
657 src = "";
658 break;
659 }
660 }
661
662 if (pmatch[0].rm_eo > 0) {
663 src += pmatch[0].rm_eo;
664 flags |= GO_REG_NOTBOL;
665 }
666
667 if (pmatch[0].rm_so == pmatch[0].rm_eo) {
668 /*
669 * We have matched a null string at the current point.
670 * This might happen searching for just an anchor, for
671 * example. Don't loop forever...
672 */
673 break;
674 }
675 }
676
677 g_free (pmatch);
678
679 if (res) {
680 if (*src)
681 g_string_append (res, src);
682 return g_string_free (res, FALSE);
683 } else {
684 return NULL;
685 }
686 }
687
688 /* ------------------------------------------------------------------------- */
689
690 static void
go_search_replace_init(GObject * obj)691 go_search_replace_init (GObject *obj)
692 {
693 }
694
695 /* ------------------------------------------------------------------------- */
696
697 static void
go_search_replace_finalize(GObject * obj)698 go_search_replace_finalize (GObject *obj)
699 {
700 GOSearchReplace *sr = (GOSearchReplace *)obj;
701
702 kill_compiled (sr);
703 g_free (sr->search_text);
704 g_free (sr->replace_text);
705
706 G_OBJECT_CLASS (parent_class)->finalize (obj);
707 }
708
709 /* ------------------------------------------------------------------------- */
710
711 static void
go_search_replace_get_property(GObject * object,guint property_id,GValue * value,GParamSpec * pspec)712 go_search_replace_get_property (GObject *object,
713 guint property_id,
714 GValue *value,
715 GParamSpec *pspec)
716 {
717 GOSearchReplace *sr = (GOSearchReplace *)object;
718
719 switch (property_id) {
720 case PROP_SEARCH_TEXT:
721 g_value_set_string (value, sr->search_text);
722 break;
723 case PROP_REPLACE_TEXT:
724 g_value_set_string (value, sr->replace_text);
725 break;
726 case PROP_IS_REGEXP:
727 g_value_set_boolean (value, sr->is_regexp);
728 break;
729 case PROP_IGNORE_CASE:
730 g_value_set_boolean (value, sr->ignore_case);
731 break;
732 case PROP_PRESERVE_CASE:
733 g_value_set_boolean (value, sr->preserve_case);
734 break;
735 case PROP_MATCH_WORDS:
736 g_value_set_boolean (value, sr->match_words);
737 break;
738 default:
739 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
740 break;
741 }
742 }
743
744 /* ------------------------------------------------------------------------- */
745 /**
746 * GOSearchReplace:
747 * @search_text: string to replace.
748 * @replace_text: string to use as replacement/
749 * @comp_search: #GORegexp
750 * @is_regexp: search text is a regular expression.
751 * @ignore_case: consider "a" and "A" the same.
752 * @preserve_case: like Emacs' case-replace.
753 * @match_words: like grep -w.
754 **/
755
756 static void
go_search_replace_set_search_text(GOSearchReplace * sr,const char * text)757 go_search_replace_set_search_text (GOSearchReplace *sr, const char *text)
758 {
759 char *text_copy = g_strdup (text);
760 g_free (sr->search_text);
761 sr->search_text = text_copy;
762 kill_compiled (sr);
763 }
764
765 static void
go_search_replace_set_replace_text(GOSearchReplace * sr,const char * text)766 go_search_replace_set_replace_text (GOSearchReplace *sr, const char *text)
767 {
768 char *text_copy = g_strdup (text);
769 g_free (sr->replace_text);
770 sr->replace_text = text_copy;
771 kill_compiled (sr);
772 }
773
774 static void
go_search_replace_set_property(GObject * object,guint property_id,GValue const * value,GParamSpec * pspec)775 go_search_replace_set_property (GObject *object,
776 guint property_id,
777 GValue const *value,
778 GParamSpec *pspec)
779 {
780 GOSearchReplace *sr = (GOSearchReplace *)object;
781
782 switch (property_id) {
783 case PROP_SEARCH_TEXT:
784 go_search_replace_set_search_text (sr, g_value_get_string (value));
785 break;
786 case PROP_REPLACE_TEXT:
787 go_search_replace_set_replace_text (sr, g_value_get_string (value));
788 break;
789 case PROP_IS_REGEXP:
790 sr->is_regexp = g_value_get_boolean (value);
791 kill_compiled (sr);
792 break;
793 case PROP_IGNORE_CASE:
794 sr->ignore_case = g_value_get_boolean (value);
795 kill_compiled (sr);
796 break;
797 case PROP_PRESERVE_CASE:
798 sr->preserve_case = g_value_get_boolean (value);
799 kill_compiled (sr);
800 break;
801 case PROP_MATCH_WORDS:
802 sr->match_words = g_value_get_boolean (value);
803 kill_compiled (sr);
804 break;
805 default:
806 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
807 break;
808 }
809 }
810
811 /* ------------------------------------------------------------------------- */
812
813 static void
go_search_replace_class_init(GObjectClass * gobject_class)814 go_search_replace_class_init (GObjectClass *gobject_class)
815 {
816 parent_class = g_type_class_peek_parent (gobject_class);
817
818 gobject_class->finalize = go_search_replace_finalize;
819 gobject_class->get_property = go_search_replace_get_property;
820 gobject_class->set_property = go_search_replace_set_property;
821
822 g_object_class_install_property
823 (gobject_class,
824 PROP_SEARCH_TEXT,
825 g_param_spec_string ("search-text",
826 _("Search Text"),
827 _("The text to search for"),
828 NULL,
829 GSF_PARAM_STATIC |
830 G_PARAM_READWRITE));
831
832 g_object_class_install_property
833 (gobject_class,
834 PROP_REPLACE_TEXT,
835 g_param_spec_string ("replace-text",
836 _("Replacement Text"),
837 _("The text to replace with"),
838 NULL,
839 GSF_PARAM_STATIC |
840 G_PARAM_READWRITE));
841 g_object_class_install_property
842 (gobject_class,
843 PROP_IS_REGEXP,
844 g_param_spec_boolean ("is-regexp",
845 _("Is Regular Expression"),
846 _("Is the search text a regular expression."),
847 FALSE,
848 GSF_PARAM_STATIC |
849 G_PARAM_READWRITE));
850 g_object_class_install_property
851 (gobject_class,
852 PROP_IGNORE_CASE,
853 g_param_spec_boolean ("ignore-case",
854 _("Ignore Case"),
855 _("Ignore the case of letters."),
856 FALSE,
857 GSF_PARAM_STATIC |
858 G_PARAM_READWRITE));
859 g_object_class_install_property
860 (gobject_class,
861 PROP_PRESERVE_CASE,
862 g_param_spec_boolean ("preserve-case",
863 _("Preserve Case"),
864 _("Preserve the case of letters."),
865 FALSE,
866 GSF_PARAM_STATIC |
867 G_PARAM_READWRITE));
868 g_object_class_install_property
869 (gobject_class,
870 PROP_MATCH_WORDS,
871 g_param_spec_boolean ("match-words",
872 _("Match Words"),
873 _("Match whole words only."),
874 FALSE,
875 GSF_PARAM_STATIC |
876 G_PARAM_READWRITE));
877 }
878
879 /* ------------------------------------------------------------------------- */
880
881 GSF_CLASS (GOSearchReplace, go_search_replace,
882 go_search_replace_class_init, go_search_replace_init, G_TYPE_OBJECT)
883