1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 
3 /*
4  *  GThumb
5  *
6  *  Copyright (C) 2019 Free Software Foundation, Inc.
7  *
8  *  This program is free software; you can redistribute it and/or modify
9  *  it under the terms of the GNU General Public License as published by
10  *  the Free Software Foundation; either version 2 of the License, or
11  *  (at your option) any later version.
12  *
13  *  This program is distributed in the hope that it will be useful,
14  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
15  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  *  GNU General Public License for more details.
17  *
18  *  You should have received a copy of the GNU General Public License
19  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
20  */
21 
22 #include <config.h>
23 #include <glib.h>
24 #include <glib/gi18n.h>
25 #include "str-utils.h"
26 
27 
28 gboolean
_g_str_equal(const char * str1,const char * str2)29 _g_str_equal (const char *str1,
30 	      const char *str2)
31 {
32 	return g_strcmp0 (str1, str2) == 0;
33 }
34 
35 
36 gboolean
_g_str_n_equal(const char * str1,const char * str2,gsize size)37 _g_str_n_equal (const char *str1,
38 		const char *str2,
39 		gsize       size)
40 {
41 	if ((str1 == NULL) && (str2 == NULL))
42 		return TRUE;
43 	if ((str1 == NULL) || (str2 == NULL))
44 		return FALSE;
45 	return strncmp (str1, str2, size);
46 }
47 
48 
49 void
_g_str_set(char ** str,const char * value)50 _g_str_set (char       **str,
51 	    const char  *value)
52 {
53 	if (*str == value)
54 		return;
55 
56 	if (*str != NULL) {
57 		g_free (*str);
58 		*str = NULL;
59 	}
60 
61 	if (value != NULL)
62 		*str = g_strdup (value);
63 }
64 
65 
66 char **
_g_strv_take_from_str_list(GList * str_list,int size)67 _g_strv_take_from_str_list (GList *str_list,
68 			    int    size)
69 {
70 	char  **str_v;
71 	GList  *scan;
72 	int     i;
73 
74 	if (size < 0)
75 		size = g_list_length (str_list);
76 
77 	str_v = g_new0 (char *, size + 1);
78 	for (scan = g_list_last (str_list), i = 0; scan && (i < size); scan = scan->prev, i++)
79 		str_v[i] = (char *) scan->data;
80 	str_v[i] = NULL;
81 
82 	return str_v;
83 }
84 
85 
86 char *
_g_str_random(int len)87 _g_str_random (int len)
88 {
89 	static char *alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
90 	static int   letters_only = 52;
91 	static int   whole_alphabet = 62;
92 
93 	GRand       *generator;
94 	char        *s;
95 	int          i;
96 
97 	generator = g_rand_new ();
98 
99 	s = g_new (char, len + 1);
100 	for (i = 0; i < len; i++)
101 		s[i] = alphabet[g_rand_int_range (generator, 0, (i == 0) ? letters_only : whole_alphabet)];
102 	s[len] = 0;
103 
104 	g_rand_free (generator);
105 
106 	return s;
107 }
108 
109 
110 char *
_g_str_remove_suffix(const char * str,const char * suffix)111 _g_str_remove_suffix (const char *str,
112 		      const char *suffix)
113 {
114 	int s_len;
115 	int suffix_len;
116 
117 	if (str == NULL)
118 		return NULL;
119 
120 	if (suffix == NULL)
121 		return g_strdup (str);
122 
123 	s_len = strlen (str);
124 	suffix_len = strlen (suffix);
125 
126 	if (suffix_len >= s_len)
127 		return g_strdup ("");
128 	else
129 		return g_strndup (str, s_len - suffix_len);
130 }
131 
132 
133 GHashTable    *static_strings = NULL;
134 static GMutex  static_strings_mutex;
135 
136 
137 const char *
_g_str_get_static(const char * str)138 _g_str_get_static (const char *str)
139 {
140 	const char *result;
141 
142 	if (str == NULL)
143 		return NULL;
144 
145 	g_mutex_lock (&static_strings_mutex);
146 
147 	if (static_strings == NULL)
148 		static_strings = g_hash_table_new_full (g_str_hash,
149 							g_str_equal,
150 							g_free,
151 							NULL);
152 
153 	if (! g_hash_table_lookup_extended (static_strings,
154 					    str,
155 					    (gpointer) &result,
156 					    NULL))
157 	{
158 		result = g_strdup (str);
159 		g_hash_table_insert (static_strings,
160 				     (gpointer) result,
161 				     GINT_TO_POINTER (1));
162 	}
163 
164 	g_mutex_unlock (&static_strings_mutex);
165 
166 	return result;
167 }
168 
169 
170 GHashTable *
_g_str_split_as_hash_table(const char * str)171 _g_str_split_as_hash_table (const char *str)
172 {
173 	GHashTable *htable;
174 
175 	htable = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
176 
177 	if (str != NULL) {
178 		char **strv;
179 		int    i;
180 
181 		strv = g_strsplit (str, ",", -1);
182 		for (i = 0; strv[i] != NULL; i++)
183 			if (strv[i] != 0)
184 				g_hash_table_add (htable, g_strdup (strv[i]));
185 
186 		g_strfreev (strv);
187 	}
188 
189 	return htable;
190 }
191 
192 
193 /* StrV utils */
194 
195 
196 int
_g_strv_find(char ** strv,const char * str)197 _g_strv_find (char       **strv,
198 	      const char  *str)
199 {
200 	int i;
201 
202 	for (i = 0; strv[i] != NULL; i++) {
203 		if (strcmp (strv[i], str) == 0)
204 			return i;
205 	}
206 
207 	return -1;
208 }
209 
210 
211 gboolean
_g_strv_contains(char ** strv,const char * str)212 _g_strv_contains (char       **strv,
213 		  const char  *str)
214 {
215 	return (_g_strv_find (strv, str) >= 0);
216 }
217 
218 
219 char **
_g_strv_prepend(char ** strv,const char * str)220 _g_strv_prepend (char       **strv,
221 		 const char  *str)
222 {
223 	char **result;
224 	int    i;
225 	int    j;
226 
227 	result = g_new (char *, g_strv_length (strv) + 1);
228 	i = 0;
229 	result[i++] = g_strdup (str);
230 	for (j = 0; strv[j] != NULL; j++)
231 		result[i++] = g_strdup (strv[j]);
232 	result[i] = NULL;
233 
234 	return result;
235 }
236 
237 
238 char **
_g_strv_concat(char ** strv1,char ** strv2)239 _g_strv_concat (char **strv1,
240 		char **strv2)
241 {
242 	char **result;
243 	int    i, j;
244 
245 	result = g_new (char *, g_strv_length (strv1) + g_strv_length (strv2) + 1);
246 	i = 0;
247 	for (j = 0; strv1[j] != NULL; j++)
248 		result[i++] = g_strdup (strv1[j]);
249 	for (j = 0; strv2[j] != NULL; j++)
250 		result[i++] = g_strdup (strv2[j]);
251 	result[i] = NULL;
252 
253 	return result;
254 }
255 
256 
257 gboolean
_g_strv_remove(char ** strv,const char * str)258 _g_strv_remove (char       **strv,
259 		const char  *str)
260 {
261 	int i;
262 	int j;
263 
264 	if (str == NULL)
265 		return FALSE;
266 
267 	for (i = 0; strv[i] != NULL; i++)
268 		if (strcmp (strv[i], str) == 0)
269 			break;
270 
271 	if (strv[i] == NULL)
272 		return FALSE;
273 
274 	for (j = i; strv[j] != NULL; j++)
275 		strv[j] = strv[j + 1];
276 
277 	return TRUE;
278 }
279 
280 
281 /* UTF-8 utils */
282 
283 
284 char *
_g_utf8_strndup(const char * str,gssize size)285 _g_utf8_strndup (const char *str,
286 		 gssize      size)
287 {
288 	char *new_str;
289 
290 	if ((str == NULL) || (size == 0))
291 		return NULL;
292 
293 	if (size < 0)
294 		size = g_utf8_strlen (str, -1);
295 
296 	new_str = g_new (char, size * 4 + 1);
297 	g_utf8_strncpy (new_str, str, size);
298 
299 	return new_str;
300 }
301 
302 
303 const char *
_g_utf8_find_str(const char * haystack,const char * needle)304 _g_utf8_find_str (const char *haystack,
305 		  const char *needle)
306 {
307 	glong haystack_len;
308 	glong needle_len;
309 	glong needle_size;
310 	glong i;
311 
312 	if ((haystack == NULL) || (needle == NULL))
313 		return NULL;
314 
315 	haystack_len = g_utf8_strlen (haystack, -1);
316 	needle_len = g_utf8_strlen (needle, -1);
317 	needle_size = strlen (needle);
318 
319 	if (needle_len == 0)
320 		return NULL;
321 
322 	for (i = 0; i <= haystack_len - needle_len; i++) {
323 		if (strncmp (haystack, needle, needle_size) == 0)
324 			return haystack;
325 		haystack = g_utf8_next_char (haystack);
326 	}
327 
328 	return NULL;
329 }
330 
331 
332 /* -- _g_utf8_split -- */
333 
334 
335 char **
_g_utf8_split(const char * str,const char * sep,int max_tokens)336 _g_utf8_split (const char *str,
337 	       const char *sep,
338 	       int         max_tokens)
339 {
340 	glong        sep_size;
341 	GList       *chunk_list;
342 	int          chunk_n;
343 	const char  *p;
344 	char       **chunk_v;
345 
346 	sep_size = (sep != NULL) ? strlen (sep) : 0;
347 	chunk_list = NULL;
348 	chunk_n = 0;
349 	p = str;
350 	while ((p != NULL) && (max_tokens != 0) && (max_tokens != 1)) {
351 		const char *sep_p = _g_utf8_find_str (p, sep);
352 		char       *chunk;
353 
354 		if (sep_p == NULL) {
355 			if ((p == str) && (*p == 0)) {
356 				/* Special case: when splitting an emtpy string
357 				 * return an emtpy string. */
358 
359 				chunk = g_strdup ("");
360 				chunk_list = g_list_prepend (chunk_list, chunk);
361 				chunk_n++;
362 				if (max_tokens > 0) max_tokens--;
363 				p = NULL;
364 			}
365 			else if ((sep != NULL) && (sep_size == 0)) {
366 
367 				/* Special case: when the separator is an
368 				 * empty string, split each character. */
369 
370 				chunk = _g_utf8_strndup (p, 1);
371 				chunk_list = g_list_prepend (chunk_list, chunk);
372 				chunk_n++;
373 				if (max_tokens > 0) max_tokens--;
374 				p = g_utf8_next_char (p);
375 			}
376 			else {
377 				chunk = g_strdup (p);
378 				chunk_list = g_list_prepend (chunk_list, chunk);
379 				chunk_n++;
380 				if (max_tokens > 0) max_tokens--;
381 				p = NULL;
382 			}
383 		}
384 		else if (sep_p > p) {
385 			chunk = g_strndup (p, sep_p - p);
386 			chunk_list = g_list_prepend (chunk_list, chunk);
387 			chunk_n++;
388 			if (max_tokens > 0) max_tokens--;
389 			p = sep_p + sep_size;
390 		}
391 		else
392 			p = sep_p + sep_size;
393 
394 		if ((p != NULL) && (g_utf8_get_char (p) == 0))
395 			break;
396 	}
397 
398 	if ((p != NULL) && (max_tokens == 1)) {
399 		chunk_list = g_list_prepend (chunk_list, g_strdup (p));
400 		chunk_n++;
401 	}
402 
403 	chunk_v = _g_strv_take_from_str_list (chunk_list, chunk_n);
404 	g_list_free (chunk_list);
405 
406 	return chunk_v;
407 }
408 
409 
410 /* -- _g_utf8_split_template -- */
411 
412 
413 typedef enum {
414 	SPLIT_TMPL_STATE_START,
415 	SPLIT_TMPL_STATE_READING_SHARPS,
416 	SPLIT_TMPL_STATE_READING_LITERAL
417 } SplitTmplState;
418 
419 
420 /**
421  * example 1 : "xxx##yy#" --> [0] = xxx
422  *                            [1] = ##
423  *                            [2] = yy
424  *                            [3] = #
425  *                            [4] = NULL
426  *
427  * example 2 : ""         --> [0] = NULL
428  **/
429 char **
_g_utf8_split_template(const char * tmpl)430 _g_utf8_split_template (const char *tmpl)
431 {
432 	SplitTmplState   state;
433 	GList           *chunk_list;
434 	int              chunk_n;
435 	const char      *p;
436 	const char      *chunk_start;
437 	const char      *chunk_end;
438 	char           **chunk_v;
439 
440 	state = SPLIT_TMPL_STATE_START;
441 	chunk_list = NULL;
442 	chunk_n = 0;
443 	p = tmpl;
444 	while (p != NULL) {
445 		gunichar ch = g_utf8_get_char (p);
446 		gboolean save_chunk = FALSE;
447 
448 		switch (state) {
449 		case SPLIT_TMPL_STATE_START:
450 			chunk_start = chunk_end = p;
451 			if (ch == '#')
452 				state = SPLIT_TMPL_STATE_READING_SHARPS;
453 			else
454 				state = SPLIT_TMPL_STATE_READING_LITERAL;
455 			break;
456 
457 		case SPLIT_TMPL_STATE_READING_SHARPS:
458 			if (ch != '#') {
459 				state = SPLIT_TMPL_STATE_READING_LITERAL;
460 				save_chunk = TRUE;
461 			}
462 			else
463 				chunk_end = p;
464 			break;
465 
466 		case SPLIT_TMPL_STATE_READING_LITERAL:
467 			if ((ch == '#') || (ch == 0)) {
468 				state = SPLIT_TMPL_STATE_READING_SHARPS;
469 				save_chunk = TRUE;
470 			}
471 			else
472 				chunk_end = p;
473 			break;
474 		}
475 
476 		if (save_chunk) {
477 			glong  chunk_size;
478 			char  *chunk;
479 
480 			chunk_size = chunk_end - chunk_start + 1;
481 			chunk = _g_utf8_strndup (chunk_start, chunk_size);
482 			chunk_list = g_list_prepend (chunk_list, chunk);
483 			chunk_n++;
484 			chunk_start = chunk_end = p;
485 		}
486 
487 		if (ch == 0)
488 			break;
489 
490 		p = g_utf8_next_char (p);
491 	}
492 
493 	chunk_v = _g_strv_take_from_str_list (chunk_list, chunk_n);
494 	g_list_free (chunk_list);
495 
496 	return chunk_v;
497 }
498 
499 
500 char *
_g_utf8_replace_str(const char * str,const char * old_str,const char * new_str)501 _g_utf8_replace_str (const char *str,
502 		     const char *old_str,
503 		     const char *new_str)
504 {
505 	GString     *result;
506 	size_t       old_str_size;
507 	const char  *p;
508 
509 	if (str == NULL)
510 		return NULL;
511 
512 	result = g_string_new ("");
513 	old_str_size = (old_str != NULL) ? strlen (old_str) : 0;
514 	p = str;
515 	while ((p != NULL) && (g_utf8_get_char (p) != 0)) {
516 		const char *sep = _g_utf8_find_str (p, old_str);
517 
518 		if (sep == NULL) {
519 			g_string_append (result, p);
520 			p = NULL;
521 		}
522 		else {
523 			g_string_append_len (result, p, sep - p);
524 			if (new_str != NULL)
525 				g_string_append (result, new_str);
526 			p = sep + old_str_size;
527 		}
528 	}
529 
530 	return g_string_free (result, FALSE);
531 }
532 
533 
534 char *
_g_utf8_replace_pattern(const char * str,const char * pattern,const char * replacement)535 _g_utf8_replace_pattern (const char *str,
536 			 const char *pattern,
537 			 const char *replacement)
538 {
539 	GRegex *regex;
540 	char   *result;
541 
542 	if (str == NULL)
543 		return NULL;
544 
545 	regex = g_regex_new (pattern, 0, 0, NULL);
546 	if (regex == NULL)
547 		return NULL;
548 
549 	result = g_regex_replace_literal (regex, str, -1, 0, replacement, 0, NULL);
550 
551 	g_regex_unref (regex);
552 
553 	return result;
554 }
555 
556 
557 char *
_g_utf8_last_char(const char * str,glong * p_size)558 _g_utf8_last_char (const char *str,
559 		   glong      *p_size)
560 {
561 	glong len;
562 
563 	if (str == NULL) {
564 		if (p_size) *p_size = 0;
565 		return NULL;
566 	}
567 
568 	len = strlen (str);
569 	if (p_size) *p_size = len;
570 
571 	if (len == 0)
572 		return NULL;
573 
574 	return g_utf8_find_prev_char (str, str + len);
575 }
576 
577 
578 gboolean
_g_utf8_n_equal(const char * str1,const char * str2,glong size)579 _g_utf8_n_equal (const char  *str1,
580 		 const char  *str2,
581 		 glong        size)
582 {
583 	const char *p1;
584 	const char *p2;
585 
586 	p1 = str1;
587 	p2 = str2;
588 	while ((size > 0) && (p1 != NULL) && (p2 != NULL)) {
589 		gunichar c1 = g_utf8_get_char (p1);
590 		gunichar c2 = g_utf8_get_char (p2);
591 
592 		if ((c1 == 0) || (c2 == 0) || (c1 != c2))
593 			break;
594 
595 		size--;
596 		p1 = g_utf8_next_char (p1);
597 		p2 = g_utf8_next_char (p2);
598 	}
599 
600 	return size == 0;
601 }
602 
603 
604 const char *
_g_utf8_after_ascii_space(const char * str)605 _g_utf8_after_ascii_space (const char *str)
606 {
607 	while (str != NULL) {
608 		gunichar c = g_utf8_get_char (str);
609 
610 		if (c == 0)
611 			break;
612 
613 		if (c == ' ')
614 			return g_utf8_next_char (str);
615 
616 		str = g_utf8_next_char (str);
617 	}
618 
619 	return NULL;
620 }
621 
622 
623 gboolean
_g_utf8_has_prefix(const char * str,const char * prefix)624 _g_utf8_has_prefix (const char *str,
625 		    const char *prefix)
626 {
627 	if (str == NULL)
628 		return FALSE;
629 
630 	if (prefix == NULL)
631 		return FALSE;
632 
633 	while ((str != NULL) && (prefix != NULL)) {
634 		gunichar str_ch = g_utf8_get_char (str);
635 		gunichar prefix_ch = g_utf8_get_char (prefix);
636 
637 		if (prefix_ch == 0)
638 			return TRUE;
639 
640 		if (str_ch == 0)
641 			return FALSE;
642 
643 		if (str_ch != prefix_ch)
644 			return FALSE;
645 
646 		str = g_utf8_next_char (str);
647 		prefix = g_utf8_next_char (prefix);
648 	}
649 
650 	return FALSE;
651 }
652 
653 
654 gboolean
_g_utf8_all_spaces(const char * str)655 _g_utf8_all_spaces (const char *str)
656 {
657 	while (str != NULL) {
658 		gunichar ch = g_utf8_get_char (str);
659 
660 		if (ch == 0)
661 			break;
662 
663 		if (! g_unichar_isspace (ch))
664 			return FALSE;
665 
666 		str = g_utf8_next_char (str);
667 	}
668 
669 	return TRUE;
670 }
671 
672 
673 char *
_g_utf8_try_from_any(const char * str)674 _g_utf8_try_from_any (const char *str)
675 {
676 	char *utf8_str;
677 
678 	if (str == NULL)
679 		return NULL;
680 
681 	if (! g_utf8_validate (str, -1, NULL))
682 		utf8_str = g_locale_to_utf8 (str, -1, NULL, NULL, NULL);
683 	else
684 		utf8_str = g_strdup (str);
685 
686 	return utf8_str;
687 }
688 
689 
690 char *
_g_utf8_from_any(const char * str)691 _g_utf8_from_any (const char *str)
692 {
693 	char *utf8_str;
694 
695 	if (str == NULL)
696 		return NULL;
697 
698 	utf8_str = _g_utf8_try_from_any (str);
699 	if (utf8_str == NULL)
700 		utf8_str = g_strdup (_("(invalid value)"));
701 
702 	return utf8_str;
703 }
704 
705 
706 /* -- _g_utf8_strip_func -- */
707 
708 
709 typedef enum {
710 	STRIP_STATE_HEADING_SPACE,
711 	STRIP_STATE_REST
712 } StripState;
713 
714 
715 char *
_g_utf8_strip_func(const char * str,StripFunc is_space_func)716 _g_utf8_strip_func (const char *str,
717 		    StripFunc   is_space_func)
718 {
719 	const char *first_non_space = NULL;
720 	const char *last_non_space = NULL;
721 	StripState  state = STRIP_STATE_HEADING_SPACE;
722 
723 	if (str == NULL)
724 		return NULL;
725 
726 	if (is_space_func == NULL)
727 		return g_strdup ("");
728 
729 	while (str != NULL) {
730 		gunichar ch = g_utf8_get_char (str);
731 		gboolean is_space = is_space_func (ch) || (ch == 0);
732 
733 		switch (state) {
734 		case STRIP_STATE_HEADING_SPACE:
735 			if (! is_space) {
736 				state = STRIP_STATE_REST;
737 				first_non_space = last_non_space = str;
738 			}
739 			break;
740 
741 		case STRIP_STATE_REST:
742 			if (! is_space)
743 				last_non_space = str;
744 			break;
745 		}
746 
747 		if (ch == 0)
748 			break;
749 
750 		str = g_utf8_next_char (str);
751 	}
752 
753 	if (first_non_space == NULL)
754 		return g_strdup ("");
755 
756 	g_assert (last_non_space != NULL);
757 
758 	return g_strndup (first_non_space, g_utf8_next_char (last_non_space) - first_non_space);
759 }
760 
761 
762 char *
_g_utf8_strip(const char * str)763 _g_utf8_strip (const char *str)
764 {
765 	return _g_utf8_strip_func (str, g_unichar_isspace);
766 }
767 
768 
769 /* -- _g_utf8_rstrip_func -- */
770 
771 
772 typedef enum {
773 	RSTRIP_STATE_ONLY_SPACES,	/* String contains only spaces. */
774 	RSTRIP_STATE_NON_SPACE,		/* Reading non space characters. */
775 	RSTRIP_STATE_TRAILING_SPACE	/* Reading possible trailing spaces. */
776 } RStripState;
777 
778 
779 char *
_g_utf8_rstrip_func(const char * str,StripFunc is_space_func)780 _g_utf8_rstrip_func (const char	 *str,
781 		     StripFunc	  is_space_func)
782 {
783 	const char  *first_trail_space;
784 	RStripState  state;
785 	const char  *p;
786 
787 	if (str == NULL)
788 		return NULL;
789 
790 	if (is_space_func == NULL)
791 		return g_strdup (str);
792 
793 	first_trail_space = NULL;
794 	state = RSTRIP_STATE_ONLY_SPACES;
795 	p = str;
796 	while (p != NULL) {
797 		gunichar ch = g_utf8_get_char (p);
798 		gboolean is_space = is_space_func (ch);
799 
800 		switch (state) {
801 		case RSTRIP_STATE_ONLY_SPACES:
802 			if (! is_space && (ch != 0))
803 				state = RSTRIP_STATE_NON_SPACE;
804 			break;
805 
806 		case RSTRIP_STATE_NON_SPACE:
807 			if (is_space) {
808 				state = RSTRIP_STATE_TRAILING_SPACE;
809 				first_trail_space = p;
810 			}
811 			break;
812 
813 		case RSTRIP_STATE_TRAILING_SPACE:
814 			if (! is_space && (ch != 0)) {
815 				state = RSTRIP_STATE_NON_SPACE;
816 				first_trail_space = NULL;
817 			}
818 			break;
819 		}
820 
821 		if (ch == 0)
822 			break;
823 
824 		p = g_utf8_next_char (p);
825 	}
826 
827 	if (state == RSTRIP_STATE_ONLY_SPACES)
828 		return g_strdup ("");
829 
830 	if (state == RSTRIP_STATE_NON_SPACE)
831 		return g_strdup (str);
832 
833 	g_assert (first_trail_space != NULL);
834 
835 	return g_strndup (str, first_trail_space - str);
836 }
837 
838 
839 char *
_g_utf8_rstrip(const char * str)840 _g_utf8_rstrip (const char *str)
841 {
842 	return _g_utf8_rstrip_func (str, g_unichar_isspace);
843 }
844 
845 
846 /* -- _g_utf8_translate -- */
847 
848 
849 static gboolean
_g_unichar_equal(gconstpointer v1,gconstpointer v2)850 _g_unichar_equal (gconstpointer v1,
851 		  gconstpointer v2)
852 {
853 	return *((const gunichar*) v1) == *((const gunichar*) v2);
854 }
855 
856 
857 static guint
_g_unichar_hash(gconstpointer v)858 _g_unichar_hash (gconstpointer v)
859 {
860 	return (guint) *(const gunichar*) v;
861 }
862 
863 
864 /* Substitute each occurrence of a character with a string. */
865 char *
_g_utf8_translate(const char * str,...)866 _g_utf8_translate (const char *str,
867 		   ...)
868 {
869 	va_list     args;
870 	GHashTable *translation;
871 	const char *arg;
872 	GString    *regexp;
873 
874 	if (str == NULL)
875 		return NULL;
876 
877 	translation = g_hash_table_new_full (_g_unichar_hash, _g_unichar_equal, NULL, g_free);
878 	va_start (args, str);
879 	while ((arg = va_arg (args, const char *)) != NULL) {
880 		gunichar    from_ch;
881 		const char *to_str;
882 
883 		from_ch = g_utf8_get_char (arg);
884 		to_str = va_arg (args, const char *);
885 		if (to_str == NULL)
886 			break;
887 
888 		g_hash_table_insert (translation, &from_ch, g_strdup (to_str));
889 	}
890 	va_end (args);
891 
892 	if (g_hash_table_size (translation) == 0) {
893 		g_hash_table_unref (translation);
894 		return g_strdup (str);
895 	}
896 
897 	regexp = g_string_new ("");
898 	while (str != NULL) {
899 		gunichar  ch = g_utf8_get_char (str);
900 		char     *replacement;
901 
902 		if (ch == 0)
903 			break;
904 
905 		replacement = g_hash_table_lookup (translation, &ch);
906 		if (replacement != NULL)
907 			g_string_append (regexp, replacement);
908 		else
909 			g_string_append_unichar (regexp, ch);
910 
911 		str = g_utf8_next_char (str);
912 	}
913 
914 	g_hash_table_unref (translation);
915 
916 	return g_string_free (regexp, FALSE);
917 }
918 
919 
920 /* -- _g_utf8_text_escape_xml -- */
921 
922 
923 typedef enum {
924 	XML_ESCAPE_DEFAULT	= 1 << 0,
925 	XML_ESCAPE_TEXT		= 1 << 1
926 } XmlEscFlags;
927 
928 
929 static char *
_g_utf8_escape_xml_flags(const char * text,XmlEscFlags flags)930 _g_utf8_escape_xml_flags (const char  *text,
931 			  XmlEscFlags  flags)
932 {
933 	GString  *str;
934 	gboolean  for_text;
935 
936 	if (text == NULL)
937 		return NULL;
938 
939 	str = g_string_sized_new (strlen (text));
940 	for_text = (flags & XML_ESCAPE_TEXT) != 0;
941 
942 	while (text != NULL) {
943 		gunichar ch = g_utf8_get_char (text);
944 
945 		if (ch == 0)
946 			break;
947 
948 		switch (ch) {
949 		case '&':
950 			g_string_append (str, "&amp;");
951 			break;
952 
953 		case '<':
954 			g_string_append (str, "&lt;");
955 			break;
956 
957 		case '>':
958 			g_string_append (str, "&gt;");
959 			break;
960 
961 		case '\'':
962 			g_string_append (str, "&apos;");
963 			break;
964 
965 		case '"':
966 			g_string_append (str, "&quot;");
967 			break;
968 
969 		default:
970 			if (for_text && (ch == '\n'))
971 				g_string_append (str, "<br>");
972 			else if ((ch > 127) || ! g_ascii_isprint ((char) ch))
973 				g_string_append_printf (str, "&#%d;", ch);
974 			else
975 				g_string_append_unichar (str, ch);
976 			break;
977 		}
978 
979 		text = g_utf8_next_char (text);
980 	}
981 
982 	return g_string_free (str, FALSE);
983 }
984 
985 
986 char *
_g_utf8_escape_xml(const char * str)987 _g_utf8_escape_xml (const char *str)
988 {
989 	return _g_utf8_escape_xml_flags (str, XML_ESCAPE_DEFAULT);
990 }
991 
992 
993 char *
_g_utf8_text_escape_xml(const char * str)994 _g_utf8_text_escape_xml	(const char *str)
995 {
996 	return _g_utf8_escape_xml_flags (str, XML_ESCAPE_TEXT);
997 }
998