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