1 /*
2  * go-string.c : ref counted shared strings with richtext and phonetic support
3  *
4  * Copyright (C) 2008 Jody Goldberg (jody@gnome.org)
5  * Copyright (C) 2007-2008 Morten Welinder (terra@gnome.org)
6  *
7  * This program 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 program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; 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 <goffice/goffice.h>
25 #include <gsf/gsf-utils.h>
26 #include <string.h>
27 
28 /**
29  * GOString:
30  * @str: the embeded UTF-8 string
31  *
32  * GOString is a structure containing a string.
33  **/
34 typedef struct {
35 	GOString	base;
36 	guint32		hash;
37 	guint32		flags;		/* len in bytes (not characters) */
38 	guint32		ref_count;
39 } GOStringImpl;
40 typedef struct _GOStringRichImpl {
41 	GOStringImpl		  base;
42 	PangoAttrList		 *markup;
43 	GOStringPhonetic	 *phonetic;
44 } GOStringRichImpl;
45 
46 #define GO_STRING_HAS_CASEFOLD	(1u << 31)
47 #define GO_STRING_HAS_COLLATE	(1u << 30)
48 #define GO_STRING_IS_RICH	(1u << 29)
49 #define GO_STRING_IS_SHARED	(1u << 28) /* rich strings share this base */
50 #define GO_STRING_IS_DEPENDENT	(1u << 27) /* a rich string that shares an underlying */
51 
52 /* mask off just the len */
53 #define GO_STRING_LEN(s)	(((GOStringImpl const *)(s))->flags & ((1u << 27) - 1u))
54 
55 /* Collection of unique strings
56  * : GOStringImpl.base.hash -> GOStringImpl * */
57 static GHashTable *go_strings_base;
58 
59 /* Collection of gslist keyed the basic string
60  * : str (directly on the pointer) -> GSList of GOStringRichImpl */
61 static GHashTable *go_strings_shared;
62 
63 static inline GOStringImpl *
go_string_impl_new(char const * str,guint32 hash,guint32 flags,guint32 ref_count)64 go_string_impl_new (char const *str, guint32 hash, guint32 flags, guint32 ref_count)
65 {
66 	GOStringImpl *res = g_slice_new (GOStringImpl);
67 	res->base.str	= str;
68 	res->hash	= hash;
69 	res->flags	= flags;
70 	res->ref_count	= ref_count;
71 	g_hash_table_replace (go_strings_base, res, res);
72 	return res;
73 }
74 
75 static void
go_string_phonetic_unref(GOStringPhonetic * phonetic)76 go_string_phonetic_unref (GOStringPhonetic *phonetic)
77 {
78 	/* TODO */
79 }
80 
81 static GOString *
replace_rich_base_with_plain(GOStringRichImpl * rich)82 replace_rich_base_with_plain (GOStringRichImpl *rich)
83 {
84 	GOStringImpl *res = go_string_impl_new (rich->base.base.str, rich->base.hash,
85 		(rich->base.flags & (~GO_STRING_IS_RICH)) | GO_STRING_IS_SHARED, 2);
86 
87 	rich->base.flags |= GO_STRING_IS_DEPENDENT;
88 	if ((rich->base.flags & GO_STRING_IS_SHARED)) {
89 		GSList *shares = g_hash_table_lookup (go_strings_shared, res->base.str);
90 		unsigned n = g_slist_length (shares);
91 		g_assert (rich->base.ref_count >= n);
92 		rich->base.flags &= ~GO_STRING_IS_SHARED;
93 		rich->base.ref_count -= n;
94 		res->ref_count += n;
95 		if (rich->base.ref_count == 0) {
96 			rich->base.ref_count = 1;
97 			go_string_unref ((GOString *) rich);
98 		} else {
99 			shares = g_slist_prepend (shares, rich);
100 			g_hash_table_replace (go_strings_shared,
101 			                      (gpointer) res->base.str, shares);
102 			n++;
103 		}
104 		if (n == 0)
105 			res->flags &= ~GO_STRING_IS_SHARED;
106 	} else
107 		g_hash_table_insert (go_strings_shared, (gpointer) res->base.str,
108 			g_slist_prepend (NULL, rich));
109 
110 	return &res->base;
111 }
112 
113 /**
114  * go_string_new_len :
115  * @str: string (optionally %NULL)
116  * @len: guint32
117  *
118  * GOString duplicates @str if no string already exists.
119  *
120  * Returns: a reference to a #GOString containing @str, or %NULL if @str is %NULL
121  **/
122 GOString *
go_string_new_len(char const * str,guint32 len)123 go_string_new_len (char const *str, guint32 len)
124 {
125 	GOStringImpl key, *res;
126 
127 	if (NULL == str)
128 		return NULL;
129 
130 	key.base.str = str;
131 	key.flags    = len;
132 	key.hash     = g_str_hash (str);
133 	res = g_hash_table_lookup (go_strings_base, &key);
134 	if (NULL == res) {
135 		 /* Copy str */
136 		res = go_string_impl_new (g_strndup (str, len),
137 					  key.hash, key.flags, 1);
138 		return &res->base;
139 	} else if (G_UNLIKELY (res->flags & GO_STRING_IS_RICH))
140 		/* if rich was there first move it to the shared */
141 		return replace_rich_base_with_plain ((GOStringRichImpl *)res);
142 	else
143 		return go_string_ref (&res->base);
144 }
145 
146 /**
147  * go_string_new_nocopy_len:
148  * @str: string (optionally %NULL)
149  * @len: guint32
150  *
151  * GOString takes ownership of @str
152  *
153  * Returns: a reference to a #GOString containing @str
154  **/
155 GOString *
go_string_new_nocopy_len(char * str,guint32 len)156 go_string_new_nocopy_len (char *str, guint32 len)
157 {
158 	GOStringImpl key, *res;
159 
160 	if (NULL == str)
161 		return NULL;
162 
163 	key.base.str = str;
164 	key.flags    = len;
165 	key.hash     = g_str_hash (str);
166 	res = g_hash_table_lookup (go_strings_base, &key);
167 	if (NULL == res) {
168 		 /* NO copy str */
169 		res = go_string_impl_new (str, key.hash, key.flags, 1);
170 		return &res->base;
171 	}
172 
173 	if (str != res->base.str) g_free (str); /* Be extra careful */
174 
175 	/* if rich was there first move it to the shared */
176 	if (G_UNLIKELY (res->flags & GO_STRING_IS_RICH))
177 		return replace_rich_base_with_plain ((GOStringRichImpl *)res);
178 
179 	return go_string_ref (&res->base);
180 }
181 
182 /**
183  * go_string_new :
184  * @str: string (optionally %NULL)
185  *
186  * GOString duplicates @str if no string already exists.
187  *
188  * Returns: a reference to a #GOString containing @str, or %NULL if @str is %NULL
189  **/
190 GOString *
go_string_new(char const * str)191 go_string_new (char const *str)
192 {
193 	return str ? go_string_new_len (str, strlen (str)) : NULL;
194 }
195 
196 /**
197  * go_string_new_nocopy:
198  * @str: string
199  *
200  * GOString takes ownership of @str
201  *
202  * Returns: a reference to a #GOString containing @str
203  **/
204 GOString *
go_string_new_nocopy(char * str)205 go_string_new_nocopy (char *str)
206 {
207 	return str ? go_string_new_nocopy_len (str, strlen (str)) : NULL;
208 }
209 
210 static GOString *
go_string_new_rich_impl(char * str,int byte_len,gboolean take_ownership,PangoAttrList * markup,GOStringPhonetic * phonetic)211 go_string_new_rich_impl (char *str,
212 			 int byte_len,
213 			 gboolean take_ownership,
214 			 PangoAttrList *markup,
215 			 GOStringPhonetic *phonetic)
216 {
217 	GOStringImpl *base;
218 	GOStringRichImpl *rich;
219 
220 	if (NULL == str) {
221 		if (NULL != markup) pango_attr_list_unref (markup);
222 		if (NULL != phonetic) go_string_phonetic_unref (phonetic);
223 		return NULL;
224 	}
225 
226 	/* TODO : when we use a better representation for attributes (eg array
227 	 * of GOFont indicies) look into sharing rich strings */
228 
229 	if (byte_len <= 0)
230 		byte_len = strlen (str);
231 	rich = g_slice_new (GOStringRichImpl);
232 	rich->base.base.str	= str;
233 	rich->base.hash		= g_str_hash (str);
234 	rich->base.flags	=  ((guint32) byte_len) | GO_STRING_IS_RICH;
235 	rich->base.ref_count	= 1;
236 	rich->markup		= markup;
237 	rich->phonetic		= phonetic;
238 	base = g_hash_table_lookup (go_strings_base, rich);
239 	if (NULL == base) {
240 		if (!take_ownership)
241 			rich->base.base.str = g_strndup (str, byte_len);
242 		g_hash_table_insert (go_strings_base, rich, rich);
243 	} else {
244 		go_string_ref (&base->base);
245 		if (take_ownership)
246 			g_free (str);
247 		rich->base.base.str = base->base.str;
248 		rich->base.flags |= GO_STRING_IS_DEPENDENT;
249 		if ((base->flags & GO_STRING_IS_SHARED)) {
250 			GSList *shares = g_hash_table_lookup (go_strings_shared, rich->base.base.str);
251 			/* ignore result, assignment is just to make the compiler shutup */
252 			shares = g_slist_insert (shares, rich, 1);
253 		} else {
254 			base->flags |= GO_STRING_IS_SHARED;
255 			g_hash_table_insert (go_strings_shared, (gpointer) rich->base.base.str,
256 				g_slist_prepend (NULL, rich));
257 		}
258 	}
259 
260 	return &rich->base.base;
261 }
262 
263 /**
264  * go_string_new_rich:
265  * @str: string.
266  * @byte_len: < 0 will call strlen.
267  * @markup: optionally %NULL list, GOString steals the ref.
268  * @phonetic: optionally %NULL list of phonetic extensions, GOString steals the ref.
269  *
270  * Returns: a string.
271  **/
272 GOString *
go_string_new_rich(char const * str,int byte_len,PangoAttrList * markup,GOStringPhonetic * phonetic)273 go_string_new_rich (char const *str,
274 		    int byte_len,
275 		    PangoAttrList *markup,
276 		    GOStringPhonetic *phonetic)
277 {
278 	return go_string_new_rich_impl ((char *)str, byte_len, FALSE,
279 					markup, phonetic);
280 }
281 
282 /**
283  * go_string_new_rich_nocopy:
284  * @str: string; GOString takes ownership
285  * @byte_len: < 0 will call strlen.
286  * @markup: optionally %NULL list, GOString steals the ref.
287  * @phonetic: optionally %NULL list of phonetic extensions, GOString steals the ref.
288  *
289  * Returns: a string.
290  **/
291 GOString *
go_string_new_rich_nocopy(char * str,int byte_len,PangoAttrList * markup,GOStringPhonetic * phonetic)292 go_string_new_rich_nocopy (char *str,
293 			   int byte_len,
294 			   PangoAttrList *markup,
295 			   GOStringPhonetic *phonetic)
296 {
297 	return go_string_new_rich_impl (str, byte_len, TRUE, markup, phonetic);
298 }
299 
300 GOString *
go_string_ref(GOString * gstr)301 go_string_ref (GOString *gstr)
302 {
303 	if (NULL != gstr)
304 		((GOStringImpl *)gstr)->ref_count++;
305 	return gstr;
306 }
307 
308 void
go_string_unref(GOString * gstr)309 go_string_unref (GOString *gstr)
310 {
311 	GOStringImpl *impl = (GOStringImpl *)gstr;
312 	if (NULL == gstr)
313 		return;
314 
315 	g_return_if_fail (impl->ref_count > 0);
316 
317 	if ((--(impl->ref_count)) == 0) {
318 		/* polite assertion failure */
319 		g_return_if_fail (!(impl->flags & GO_STRING_IS_SHARED));
320 
321 		if ((impl->flags & GO_STRING_IS_RICH)) {
322 			GOStringRichImpl *rich = (GOStringRichImpl *)gstr;
323 			if (NULL != rich->markup) pango_attr_list_unref (rich->markup);
324 			if (NULL != rich->phonetic) go_string_phonetic_unref (rich->phonetic);
325 		}
326 
327 		if (G_UNLIKELY (impl->flags & GO_STRING_IS_DEPENDENT)) {
328 			GOStringImpl *base = g_hash_table_lookup (go_strings_base, gstr);
329 			GSList *shares = g_hash_table_lookup (go_strings_shared, gstr->str);
330 			GSList *new_shares = g_slist_remove (shares, gstr);
331 			if (new_shares != shares) {
332 				if (new_shares == NULL) {
333 					base->flags &= ~GO_STRING_IS_SHARED;
334 					g_hash_table_remove (go_strings_shared, gstr->str);
335 				} else
336 					g_hash_table_replace (go_strings_shared, (gpointer)gstr->str, new_shares);
337 			}
338 			go_string_unref (&base->base);
339 		} else {
340 			g_hash_table_remove (go_strings_base, gstr);
341 			g_free ((gpointer)gstr->str);
342 		}
343 		g_slice_free1 ((impl->flags & GO_STRING_IS_RICH?
344 		        		sizeof (GOStringRichImpl): sizeof (GOStringImpl)), gstr);
345 	}
346 }
347 
348 unsigned int
go_string_get_ref_count(GOString const * gstr)349 go_string_get_ref_count (GOString const *gstr)
350 {
351 	return gstr ? ((GOStringImpl const *)gstr)->ref_count : 0;
352 }
353 
354 guint32
go_string_hash(gconstpointer gstr)355 go_string_hash (gconstpointer gstr)
356 {
357 	return gstr ? ((GOStringImpl const *)gstr)->hash : 0;
358 }
359 
360 int
go_string_cmp(gconstpointer gstr_a,gconstpointer gstr_b)361 go_string_cmp (gconstpointer gstr_a, gconstpointer gstr_b)
362 {
363 	return (gstr_a == gstr_b)
364 		? 0
365 		: strcmp (go_string_get_collation (gstr_a),
366 			  go_string_get_collation (gstr_b));
367 }
368 
369 int
go_string_cmp_ignorecase(gconstpointer gstr_a,gconstpointer gstr_b)370 go_string_cmp_ignorecase (gconstpointer gstr_a, gconstpointer gstr_b)
371 {
372 	return (gstr_a == gstr_b)
373 		? 0
374 		: strcmp (go_string_get_casefolded_collate (gstr_a),
375 			  go_string_get_casefolded_collate (gstr_b));
376 }
377 
378 gboolean
go_string_equal(gconstpointer gstr_a,gconstpointer gstr_b)379 go_string_equal (gconstpointer gstr_a, gconstpointer gstr_b)
380 {
381 	GOString const *a = gstr_a;
382 	GOString const *b = gstr_b;
383 	return a == b || (NULL != a && NULL != b && a->str == b->str);
384 }
385 
386 static void
go_string_impl_append_extra(GOStringImpl * gstri,char * extra,unsigned int offset)387 go_string_impl_append_extra (GOStringImpl *gstri, char *extra, unsigned int offset)
388 {
389 	guint32 len = strlen (extra);
390 	gchar *res = g_realloc ((gpointer)gstri->base.str, offset + 4 + len + 1);
391 	GSF_LE_SET_GUINT32(res + offset, len);
392 	memcpy ((gpointer)(res + offset + 4), extra, len + 1);
393 	g_free (extra);
394 
395 	if (res != gstri->base.str) {
396 		/* update any shared strings */
397 		if ((gstri->flags & GO_STRING_IS_SHARED)) {
398 			GSList *shares = g_hash_table_lookup (go_strings_shared, gstri->base.str);
399 			g_hash_table_remove (go_strings_shared, gstri->base.str);
400 			g_hash_table_insert (go_strings_shared, res, shares);
401 			for (; shares != NULL ; shares = shares->next)
402 				((GOStringImpl *)(shares->data))->base.str = res;
403 		}
404 		((GOStringImpl *)gstri)->base.str = res;
405 	}
406 }
407 
408 char const *
go_string_get_collation(GOString const * gstr)409 go_string_get_collation  (GOString const *gstr)
410 {
411 	GOStringImpl *gstri = (GOStringImpl *)gstr;
412 	unsigned int len;
413 
414 	if (NULL == gstr)
415 		return "";
416 
417 	len = GO_STRING_LEN (gstri);
418 	if (0 == (gstri->flags & GO_STRING_HAS_COLLATE)) {
419 		gchar *collate = g_utf8_collate_key (gstri->base.str, len);
420 		/* Keep it simple, drop the casefold to avoid issues with overlapping */
421 		gstri->flags &= ~GO_STRING_HAS_CASEFOLD;
422 		gstri->flags |=  GO_STRING_HAS_COLLATE;
423 		go_string_impl_append_extra (gstri, collate, len + 1);
424 	}
425 	return gstri->base.str + len + 1 + 4;
426 }
427 
428 char const *
go_string_get_casefold(GOString const * gstr)429 go_string_get_casefold (GOString const *gstr)
430 {
431 	GOStringImpl *gstri = (GOStringImpl *)gstr;
432 	unsigned int offset;
433 
434 	if (NULL == gstr)
435 		return "";
436 
437 	offset = GO_STRING_LEN (gstri) + 1;
438 	if (0 != (gstri->flags & GO_STRING_HAS_COLLATE))
439 		offset += GSF_LE_GET_GUINT32(gstri->base.str + offset) + 4 + 1;
440 
441 	if (0 == (gstri->flags & GO_STRING_HAS_CASEFOLD))
442 		go_string_get_casefolded_collate (gstr);
443 	return gstri->base.str + offset + 4;
444 }
445 
446 char const *
go_string_get_casefolded_collate(GOString const * gstr)447 go_string_get_casefolded_collate (GOString const *gstr)
448 {
449 	GOStringImpl *gstri = (GOStringImpl *)gstr;
450 	unsigned int offset;
451 
452 	if (NULL == gstr)
453 		return "";
454 
455 	offset = GO_STRING_LEN (gstri) + 1;
456 	if (0 != (gstri->flags & GO_STRING_HAS_COLLATE))
457 		offset += GSF_LE_GET_GUINT32(gstri->base.str + offset) + 4 + 1;
458 
459 	if (0 == (gstri->flags & GO_STRING_HAS_CASEFOLD)) {
460 		char *collate;
461 		int len;
462 		gchar *casefold = g_utf8_casefold (gstri->base.str, GO_STRING_LEN (gstri));
463 		go_string_impl_append_extra (gstri, casefold, offset);
464 		len = GSF_LE_GET_GUINT32(gstri->base.str + offset);
465 		collate = g_utf8_collate_key (gstri->base.str + offset + 4, len);
466 		offset += len + 4 + 1;
467 		gstri->flags |= GO_STRING_HAS_CASEFOLD;
468 		go_string_impl_append_extra (gstri, collate, offset);
469 	} else
470 		offset += GSF_LE_GET_GUINT32(gstri->base.str + offset) + 4 + 1;
471 	return gstri->base.str + offset + 4;
472 }
473 
474 static GOString *go_string_ERROR_val = NULL;
475 
476 /**
477  * go_string_ERROR:
478  *
479  * A convenience for g_return_val to share one error string without adding a
480  * reference to functions that do not add references to the result
481  *
482  * Returns: A string saying 'ERROR' but does not add a ref to it.
483  **/
484 GOString *
go_string_ERROR(void)485 go_string_ERROR (void)
486 {
487 	return go_string_ERROR_val;
488 }
489 
490 /*******************************************************************************/
491 
492 /* Internal comparison routine that actually does a strcmp, rather than
493  * assuming that only str == str are equal  */
494 static gboolean
go_string_equal_internal(gconstpointer gstr_a,gconstpointer gstr_b)495 go_string_equal_internal (gconstpointer gstr_a, gconstpointer gstr_b)
496 {
497 	GOStringImpl const *a = gstr_a;
498 	GOStringImpl const *b = gstr_b;
499 	return a == b ||
500 		((a->hash == b->hash) &&
501 		 (GO_STRING_LEN (a) == GO_STRING_LEN (b)) &&
502 		 0 == strcmp (a->base.str, b->base.str));
503 }
504 
505 /**
506  * _go_string_init: (skip)
507  */
508 void
_go_string_init(void)509 _go_string_init (void)
510 {
511 	go_strings_base   = g_hash_table_new (go_string_hash, go_string_equal_internal);
512 	go_strings_shared = g_hash_table_new (g_direct_hash, g_direct_equal);
513 
514 	/* Do it here, so we always have it.  */
515 	go_string_ERROR_val = go_string_new ("<ERROR>");
516 }
517 
518 static gboolean
cb_string_pool_leak(G_GNUC_UNUSED gpointer key,gpointer value,G_GNUC_UNUSED gpointer user)519 cb_string_pool_leak (G_GNUC_UNUSED gpointer key,
520 		     gpointer value,
521 		     G_GNUC_UNUSED gpointer user)
522 {
523 	GOString const *gstr = value;
524 	g_printerr ("Leaking string [%s] with ref_count=%d.\n",
525 		    gstr->str, go_string_get_ref_count (gstr));
526 	return TRUE;
527 }
528 
529 /**
530  * _go_string_shutdown: (skip)
531  */
532 void
_go_string_shutdown(void)533 _go_string_shutdown (void)
534 {
535 	go_string_unref (go_string_ERROR_val);
536 	go_string_ERROR_val = NULL;
537 
538 	g_hash_table_destroy (go_strings_shared);
539 	go_strings_shared = NULL;
540 
541 	g_hash_table_foreach_remove (go_strings_base,
542 				     cb_string_pool_leak,
543 				     NULL);
544 	g_hash_table_destroy (go_strings_base);
545 	go_strings_base = NULL;
546 }
547 
548 static void
cb_collect_strings(G_GNUC_UNUSED gpointer key,gpointer str,gpointer user_data)549 cb_collect_strings (G_GNUC_UNUSED gpointer key, gpointer str,
550 		    gpointer user_data)
551 {
552 	GSList **pstrs = user_data;
553 	*pstrs = g_slist_prepend (*pstrs, str);
554 }
555 
556 static gint
cb_by_refcount_str(gconstpointer a_,gconstpointer b_)557 cb_by_refcount_str (gconstpointer a_, gconstpointer b_)
558 {
559 	GOStringImpl const *a = a_;
560 	GOStringImpl const *b = b_;
561 
562 	if (a->ref_count == b->ref_count)
563 		return strcmp (a->base.str, b->base.str);
564 	return (a->ref_count - b->ref_count);
565 }
566 
567 /**
568  * _go_string_dump:
569  *
570  * Internal debugging utility to
571  **/
572 void
_go_string_dump(void)573 _go_string_dump (void)
574 {
575 	GSList *strs = NULL;
576 	GSList *l;
577 	int refs = 0, len = 0;
578 	int count;
579 
580 	g_hash_table_foreach (go_strings_base, cb_collect_strings, &strs);
581 	strs = g_slist_sort (strs, cb_by_refcount_str);
582 	count = g_slist_length (strs);
583 	for (l = strs; l; l = l->next) {
584 		GOStringImpl const *s = l->data;
585 		refs += s->ref_count;
586 		len  += GO_STRING_LEN (s);
587 	}
588 
589 	for (l = g_slist_nth (strs, MAX (0, count - 100)); l; l = l->next) {
590 		GOStringImpl const *s = l->data;
591 		g_print ("%8d \"%s\"\n", s->ref_count, s->base.str);
592 	}
593 	g_print ("String table contains %d different strings.\n", count);
594 	g_print ("String table contains a total of %d characters.\n", len);
595 	g_print ("String table contains a total of %d refs.\n", refs);
596 	g_slist_free (strs);
597 }
598 
599 /**
600  * go_string_foreach_base:
601  * @callback: (scope call): callback
602  * @data: user data
603  *
604  * Iterates through the strings data base and apply @callback to each.
605  **/
606 void
go_string_foreach_base(GHFunc callback,gpointer data)607 go_string_foreach_base (GHFunc callback, gpointer data)
608 {
609 	g_hash_table_foreach (go_strings_base, callback, data);
610 }
611 
612 static void
value_transform_gostring_string(GValue const * src_val,GValue * dest)613 value_transform_gostring_string (GValue const *src_val,
614 				 GValue       *dest)
615 {
616 	GOString *src_str = src_val->data[0].v_pointer;
617 	dest->data[0].v_pointer = src_str
618 		? g_strndup (src_str->str, GO_STRING_LEN (src_str))
619 		: NULL;
620 }
621 
622 /**
623  * go_string_get_type:
624  *
625  * Register #GOString as a type for #GValue
626  *
627  * Returns: #GType
628  **/
629 GType
go_string_get_type(void)630 go_string_get_type (void)
631 {
632 	static GType t = 0;
633 
634 	if (t == 0) {
635 		t = g_boxed_type_register_static ("GOString",
636 			 (GBoxedCopyFunc)go_string_ref,
637 			 (GBoxedFreeFunc)go_string_unref);
638 		g_value_register_transform_func (t, G_TYPE_STRING, value_transform_gostring_string);
639 	}
640 	return t;
641 }
642 
643 /**
644  * go_string_get_len:
645  * @gstr: string.
646  **/
647 guint32
go_string_get_len(GOString const * gstr)648 go_string_get_len (GOString const *gstr)
649 {
650 	return GO_STRING_LEN(gstr);
651 }
652 
653 
654 /**
655  * go_string_get_markup:
656  * @gstr: string.
657  **/
658 PangoAttrList *
go_string_get_markup(GOString const * gstr)659 go_string_get_markup (GOString const *gstr)
660 {
661 	GOStringImpl *impl = (GOStringImpl *)gstr;
662 
663 	if ((impl->flags & GO_STRING_IS_RICH) != 0) {
664 		GOStringRichImpl *rich = (GOStringRichImpl *) gstr;
665 		return rich->markup;
666 	} else
667 		return NULL;
668 }
669 
670 /**
671  * go_string_get_phonetic: (skip)
672  * @gstr: #GOString.
673  *
674  * Warning: Not implemented, always returns NULL.
675  * Returns: (transfer none): the phonetic data.
676  **/
677 GOStringPhonetic *
go_string_get_phonetic(GOString const * gstr)678 go_string_get_phonetic (GOString const *gstr)
679 {
680 	GOStringImpl *impl = (GOStringImpl *)gstr;
681 
682 	if ((impl->flags & GO_STRING_IS_RICH) != 0) {
683 		GOStringRichImpl *rich = (GOStringRichImpl *) gstr;
684 		return rich->phonetic;
685 	} else
686 		return NULL;
687 }
688 
689 
690 /**
691  * go_string_equal_ignorecase:
692  * @gstr_a: string.
693  * @gstr_b: string.
694  *
695  * Returns: TRUE if the two strings are equal when ignoring letter case.
696  **/
697 gboolean
go_string_equal_ignorecase(gconstpointer gstr_a,gconstpointer gstr_b)698 go_string_equal_ignorecase (gconstpointer gstr_a, gconstpointer gstr_b)
699 {
700 	return (0 == go_string_cmp_ignorecase (gstr_a, gstr_b));
701 }
702 
703 
704 /**
705  * go_string_equal_rich:
706  * @gstr_a: string.
707  * @gstr_b: string.
708  **/
709 gboolean
go_string_equal_rich(gconstpointer gstr_a,gconstpointer gstr_b)710 go_string_equal_rich (gconstpointer gstr_a, gconstpointer gstr_b)
711 {
712 	/* TODO  */
713 
714 	return go_string_equal (gstr_a, gstr_b);
715 }
716 
717 static gboolean
find_shape_attr(PangoAttribute * attribute,G_GNUC_UNUSED gpointer data)718 find_shape_attr (PangoAttribute *attribute, G_GNUC_UNUSED gpointer data)
719 {
720 	return (attribute->klass->type == PANGO_ATTR_SHAPE);
721 }
722 
723 /**
724  * go_string_trim:
725  * @gstr: string.
726  * @internal: Trim multiple consequtive internal spaces.
727  *
728  * Returns: @gstr
729  **/
730 GOString *
go_string_trim(GOString * gstr,gboolean internal)731 go_string_trim (GOString *gstr, gboolean internal)
732 {
733 	/*TODO: handle phonetics */
734 	GOStringImpl *impl = (GOStringImpl *)gstr;
735 	char *text = NULL;
736 	char const *ctext;
737 	PangoAttrList *attrs;
738 	char const *t;
739 	int cnt, len;
740 
741 	if ((impl->flags & GO_STRING_IS_RICH) == 0)
742 		return gstr;
743 
744 	attrs = go_string_get_markup (gstr);
745 	t = ctext = text = g_strdup (gstr->str);
746 	if (attrs != NULL)
747 		attrs = pango_attr_list_copy (attrs);
748 	while (*t != 0 && *t == ' ')
749 		t++;
750 	cnt = t - text;
751 	if (cnt > 0) {
752 		len = strlen (t);
753 		memmove (text, t, len + 1);
754 		go_pango_attr_list_erase (attrs, 0, cnt);
755 	} else
756 		len = strlen(ctext);
757 	t = ctext + len - 1;
758 	while (t > ctext && *t == ' ')
759 		t--;
760 	cnt = ((t - ctext) + 1);
761 	if (len > cnt) {
762 		text[cnt] = '\0';
763 		go_pango_attr_list_erase (attrs, cnt, len - cnt);
764 	}
765 
766 	if (internal) {
767 		PangoAttrList *at = pango_attr_list_filter (attrs, find_shape_attr, NULL);
768 		char *tt = text;
769 		if (at) pango_attr_list_unref (at);
770 		while (NULL != (tt = strchr (tt, ' '))) {
771 			if (tt[1] == ' ') {
772 				go_pango_attr_list_erase (attrs, tt - text, 1);
773 				memmove (tt + 1, tt + 2, strlen(tt + 2) + 1);
774 				continue;
775 			}
776 			tt++;
777 		}
778 	}
779 
780 	go_string_unref (gstr);
781 
782 	return go_string_new_rich_nocopy (text, -1, attrs, NULL);
783 }
784