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