build: Enable -fno-strict-aliasing
[glib.git] / glib / gunicollate.c
blob161a2de824377273e0f625f22a96d758214a43e7
1 /* gunicollate.c - Collation
3 * Copyright 2001,2005 Red Hat, Inc.
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this library; if not, see <http://www.gnu.org/licenses/>.
19 #include "config.h"
21 #include <locale.h>
22 #include <string.h>
23 #ifdef __STDC_ISO_10646__
24 #include <wchar.h>
25 #endif
27 #ifdef HAVE_CARBON
28 #include <CoreServices/CoreServices.h>
29 #endif
31 #include "gmem.h"
32 #include "gunicode.h"
33 #include "gunicodeprivate.h"
34 #include "gstring.h"
35 #include "gstrfuncs.h"
36 #include "gtestutils.h"
37 #include "gcharset.h"
38 #ifndef __STDC_ISO_10646__
39 #include "gconvert.h"
40 #endif
43 #ifdef _MSC_VER
44 /* Workaround for bug in MSVCR80.DLL */
45 static gsize
46 msc_strxfrm_wrapper (char *string1,
47 const char *string2,
48 gsize count)
50 if (!string1 || count <= 0)
52 char tmp;
54 return strxfrm (&tmp, string2, 1);
56 return strxfrm (string1, string2, count);
58 #define strxfrm msc_strxfrm_wrapper
59 #endif
61 /**
62 * g_utf8_collate:
63 * @str1: a UTF-8 encoded string
64 * @str2: a UTF-8 encoded string
66 * Compares two strings for ordering using the linguistically
67 * correct rules for the [current locale][setlocale].
68 * When sorting a large number of strings, it will be significantly
69 * faster to obtain collation keys with g_utf8_collate_key() and
70 * compare the keys with strcmp() when sorting instead of sorting
71 * the original strings.
73 * Returns: < 0 if @str1 compares before @str2,
74 * 0 if they compare equal, > 0 if @str1 compares after @str2.
75 **/
76 gint
77 g_utf8_collate (const gchar *str1,
78 const gchar *str2)
80 gint result;
82 #ifdef HAVE_CARBON
84 UniChar *str1_utf16;
85 UniChar *str2_utf16;
86 glong len1;
87 glong len2;
88 SInt32 retval = 0;
90 g_return_val_if_fail (str1 != NULL, 0);
91 g_return_val_if_fail (str2 != NULL, 0);
93 str1_utf16 = g_utf8_to_utf16 (str1, -1, NULL, &len1, NULL);
94 str2_utf16 = g_utf8_to_utf16 (str2, -1, NULL, &len2, NULL);
96 UCCompareTextDefault (kUCCollateStandardOptions,
97 str1_utf16, len1, str2_utf16, len2,
98 NULL, &retval);
99 result = retval;
101 g_free (str2_utf16);
102 g_free (str1_utf16);
104 #elif defined(__STDC_ISO_10646__)
106 gunichar *str1_norm;
107 gunichar *str2_norm;
109 g_return_val_if_fail (str1 != NULL, 0);
110 g_return_val_if_fail (str2 != NULL, 0);
112 str1_norm = _g_utf8_normalize_wc (str1, -1, G_NORMALIZE_ALL_COMPOSE);
113 str2_norm = _g_utf8_normalize_wc (str2, -1, G_NORMALIZE_ALL_COMPOSE);
115 result = wcscoll ((wchar_t *)str1_norm, (wchar_t *)str2_norm);
117 g_free (str1_norm);
118 g_free (str2_norm);
120 #else /* !__STDC_ISO_10646__ */
122 const gchar *charset;
123 gchar *str1_norm;
124 gchar *str2_norm;
126 g_return_val_if_fail (str1 != NULL, 0);
127 g_return_val_if_fail (str2 != NULL, 0);
129 str1_norm = g_utf8_normalize (str1, -1, G_NORMALIZE_ALL_COMPOSE);
130 str2_norm = g_utf8_normalize (str2, -1, G_NORMALIZE_ALL_COMPOSE);
132 if (g_get_charset (&charset))
134 result = strcoll (str1_norm, str2_norm);
136 else
138 gchar *str1_locale = g_convert (str1_norm, -1, charset, "UTF-8", NULL, NULL, NULL);
139 gchar *str2_locale = g_convert (str2_norm, -1, charset, "UTF-8", NULL, NULL, NULL);
141 if (str1_locale && str2_locale)
142 result = strcoll (str1_locale, str2_locale);
143 else if (str1_locale)
144 result = -1;
145 else if (str2_locale)
146 result = 1;
147 else
148 result = strcmp (str1_norm, str2_norm);
150 g_free (str1_locale);
151 g_free (str2_locale);
154 g_free (str1_norm);
155 g_free (str2_norm);
157 #endif /* __STDC_ISO_10646__ */
159 return result;
162 #if defined(__STDC_ISO_10646__)
163 /* We need UTF-8 encoding of numbers to encode the weights if
164 * we are using wcsxfrm. However, we aren't encoding Unicode
165 * characters, so we can't simply use g_unichar_to_utf8.
167 * The following routine is taken (with modification) from GNU
168 * libc's strxfrm routine:
170 * Copyright (C) 1995-1999,2000,2001 Free Software Foundation, Inc.
171 * Written by Ulrich Drepper <drepper@cygnus.com>, 1995.
173 static inline int
174 utf8_encode (char *buf, wchar_t val)
176 int retval;
178 if (val < 0x80)
180 if (buf)
181 *buf++ = (char) val;
182 retval = 1;
184 else
186 int step;
188 for (step = 2; step < 6; ++step)
189 if ((val & (~(guint32)0 << (5 * step + 1))) == 0)
190 break;
191 retval = step;
193 if (buf)
195 *buf = (unsigned char) (~0xff >> step);
196 --step;
199 buf[step] = 0x80 | (val & 0x3f);
200 val >>= 6;
202 while (--step > 0);
203 *buf |= val;
207 return retval;
209 #endif /* __STDC_ISO_10646__ */
211 #ifdef HAVE_CARBON
213 static gchar *
214 collate_key_to_string (UCCollationValue *key,
215 gsize key_len)
217 gchar *result;
218 gsize result_len;
219 long *lkey = (long *) key;
221 /* UCCollationValue format:
223 * UCCollateOptions (32/64 bits)
224 * SizeInBytes (32/64 bits)
225 * Value (8 bits arrey)
227 * UCCollateOptions: ordering option mask of the collator
228 * used to create the key. Size changes on 32-bit / 64-bit
229 * hosts. On 64-bits also the extra half-word seems to have
230 * some extra (unknown) meaning.
231 * SizeInBytes: size of the whole structure, in bytes
232 * (including UCCollateOptions and SizeInBytes fields). Size
233 * changes on 32-bit & 64-bit hosts.
234 * Value: array of bytes containing the comparison weights.
235 * Seems to have several sub-strings separated by \001 and \002
236 * chars. Also, experience shows this is directly strcmp-able.
239 result_len = lkey[1];
240 result = g_malloc (result_len + 1);
241 memcpy (result, &lkey[2], result_len);
242 result[result_len] = '\0';
244 return result;
247 static gchar *
248 carbon_collate_key_with_collator (const gchar *str,
249 gssize len,
250 CollatorRef collator)
252 UniChar *str_utf16 = NULL;
253 glong len_utf16;
254 OSStatus ret;
255 UCCollationValue staticbuf[512];
256 UCCollationValue *freeme = NULL;
257 UCCollationValue *buf;
258 ItemCount buf_len;
259 ItemCount key_len;
260 ItemCount try_len;
261 gchar *result = NULL;
263 str_utf16 = g_utf8_to_utf16 (str, len, NULL, &len_utf16, NULL);
264 try_len = len_utf16 * 5 + 2;
266 if (try_len <= sizeof staticbuf)
268 buf = staticbuf;
269 buf_len = sizeof staticbuf;
271 else
273 freeme = g_new (UCCollationValue, try_len);
274 buf = freeme;
275 buf_len = try_len;
278 ret = UCGetCollationKey (collator, str_utf16, len_utf16,
279 buf_len, &key_len, buf);
281 if (ret == kCollateBufferTooSmall)
283 freeme = g_renew (UCCollationValue, freeme, try_len * 2);
284 buf = freeme;
285 buf_len = try_len * 2;
286 ret = UCGetCollationKey (collator, str_utf16, len_utf16,
287 buf_len, &key_len, buf);
290 if (ret == 0)
291 result = collate_key_to_string (buf, key_len);
292 else
293 result = g_strdup ("");
295 g_free (freeme);
296 g_free (str_utf16);
297 return result;
300 static gchar *
301 carbon_collate_key (const gchar *str,
302 gssize len)
304 static CollatorRef collator;
306 if (G_UNLIKELY (!collator))
308 UCCreateCollator (NULL, 0, kUCCollateStandardOptions, &collator);
310 if (!collator)
312 static gboolean been_here;
313 if (!been_here)
314 g_warning ("%s: UCCreateCollator failed", G_STRLOC);
315 been_here = TRUE;
316 return g_strdup ("");
320 return carbon_collate_key_with_collator (str, len, collator);
323 static gchar *
324 carbon_collate_key_for_filename (const gchar *str,
325 gssize len)
327 static CollatorRef collator;
329 if (G_UNLIKELY (!collator))
331 /* http://developer.apple.com/qa/qa2004/qa1159.html */
332 UCCreateCollator (NULL, 0,
333 kUCCollateComposeInsensitiveMask
334 | kUCCollateWidthInsensitiveMask
335 | kUCCollateCaseInsensitiveMask
336 | kUCCollateDigitsOverrideMask
337 | kUCCollateDigitsAsNumberMask
338 | kUCCollatePunctuationSignificantMask,
339 &collator);
341 if (!collator)
343 static gboolean been_here;
344 if (!been_here)
345 g_warning ("%s: UCCreateCollator failed", G_STRLOC);
346 been_here = TRUE;
347 return g_strdup ("");
351 return carbon_collate_key_with_collator (str, len, collator);
354 #endif /* HAVE_CARBON */
357 * g_utf8_collate_key:
358 * @str: a UTF-8 encoded string.
359 * @len: length of @str, in bytes, or -1 if @str is nul-terminated.
361 * Converts a string into a collation key that can be compared
362 * with other collation keys produced by the same function using
363 * strcmp().
365 * The results of comparing the collation keys of two strings
366 * with strcmp() will always be the same as comparing the two
367 * original keys with g_utf8_collate().
369 * Note that this function depends on the [current locale][setlocale].
371 * Returns: a newly allocated string. This string should
372 * be freed with g_free() when you are done with it.
374 gchar *
375 g_utf8_collate_key (const gchar *str,
376 gssize len)
378 gchar *result;
380 #ifdef HAVE_CARBON
382 g_return_val_if_fail (str != NULL, NULL);
383 result = carbon_collate_key (str, len);
385 #elif defined(__STDC_ISO_10646__)
387 gsize xfrm_len;
388 gunichar *str_norm;
389 wchar_t *result_wc;
390 gsize i;
391 gsize result_len = 0;
393 g_return_val_if_fail (str != NULL, NULL);
395 str_norm = _g_utf8_normalize_wc (str, len, G_NORMALIZE_ALL_COMPOSE);
397 xfrm_len = wcsxfrm (NULL, (wchar_t *)str_norm, 0);
398 result_wc = g_new (wchar_t, xfrm_len + 1);
399 wcsxfrm (result_wc, (wchar_t *)str_norm, xfrm_len + 1);
401 for (i = 0; i < xfrm_len; i++)
402 result_len += utf8_encode (NULL, result_wc[i]);
404 result = g_malloc (result_len + 1);
405 result_len = 0;
406 for (i = 0; i < xfrm_len; i++)
407 result_len += utf8_encode (result + result_len, result_wc[i]);
409 result[result_len] = '\0';
411 g_free (result_wc);
412 g_free (str_norm);
414 return result;
415 #else /* !__STDC_ISO_10646__ */
417 gsize xfrm_len;
418 const gchar *charset;
419 gchar *str_norm;
421 g_return_val_if_fail (str != NULL, NULL);
423 str_norm = g_utf8_normalize (str, len, G_NORMALIZE_ALL_COMPOSE);
425 result = NULL;
427 if (g_get_charset (&charset))
429 xfrm_len = strxfrm (NULL, str_norm, 0);
430 if (xfrm_len >= 0 && xfrm_len < G_MAXINT - 2)
432 result = g_malloc (xfrm_len + 1);
433 strxfrm (result, str_norm, xfrm_len + 1);
436 else
438 gchar *str_locale = g_convert (str_norm, -1, charset, "UTF-8", NULL, NULL, NULL);
440 if (str_locale)
442 xfrm_len = strxfrm (NULL, str_locale, 0);
443 if (xfrm_len < 0 || xfrm_len >= G_MAXINT - 2)
445 g_free (str_locale);
446 str_locale = NULL;
449 if (str_locale)
451 result = g_malloc (xfrm_len + 2);
452 result[0] = 'A';
453 strxfrm (result + 1, str_locale, xfrm_len + 1);
455 g_free (str_locale);
459 if (!result)
461 xfrm_len = strlen (str_norm);
462 result = g_malloc (xfrm_len + 2);
463 result[0] = 'B';
464 memcpy (result + 1, str_norm, xfrm_len);
465 result[xfrm_len+1] = '\0';
468 g_free (str_norm);
469 #endif /* __STDC_ISO_10646__ */
471 return result;
474 /* This is a collation key that is very very likely to sort before any
475 * collation key that libc strxfrm generates. We use this before any
476 * special case (dot or number) to make sure that its sorted before
477 * anything else.
479 #define COLLATION_SENTINEL "\1\1\1"
482 * g_utf8_collate_key_for_filename:
483 * @str: a UTF-8 encoded string.
484 * @len: length of @str, in bytes, or -1 if @str is nul-terminated.
486 * Converts a string into a collation key that can be compared
487 * with other collation keys produced by the same function using strcmp().
489 * In order to sort filenames correctly, this function treats the dot '.'
490 * as a special case. Most dictionary orderings seem to consider it
491 * insignificant, thus producing the ordering "event.c" "eventgenerator.c"
492 * "event.h" instead of "event.c" "event.h" "eventgenerator.c". Also, we
493 * would like to treat numbers intelligently so that "file1" "file10" "file5"
494 * is sorted as "file1" "file5" "file10".
496 * Note that this function depends on the [current locale][setlocale].
498 * Returns: a newly allocated string. This string should
499 * be freed with g_free() when you are done with it.
501 * Since: 2.8
503 gchar *
504 g_utf8_collate_key_for_filename (const gchar *str,
505 gssize len)
507 #ifndef HAVE_CARBON
508 GString *result;
509 GString *append;
510 const gchar *p;
511 const gchar *prev;
512 const gchar *end;
513 gchar *collate_key;
514 gint digits;
515 gint leading_zeros;
518 * How it works:
520 * Split the filename into collatable substrings which do
521 * not contain [.0-9] and special-cased substrings. The collatable
522 * substrings are run through the normal g_utf8_collate_key() and the
523 * resulting keys are concatenated with keys generated from the
524 * special-cased substrings.
526 * Special cases: Dots are handled by replacing them with '\1' which
527 * implies that short dot-delimited substrings are before long ones,
528 * e.g.
530 * a\1a (a.a)
531 * a-\1a (a-.a)
532 * aa\1a (aa.a)
534 * Numbers are handled by prepending to each number d-1 superdigits
535 * where d = number of digits in the number and SUPERDIGIT is a
536 * character with an integer value higher than any digit (for instance
537 * ':'). This ensures that single-digit numbers are sorted before
538 * double-digit numbers which in turn are sorted separately from
539 * triple-digit numbers, etc. To avoid strange side-effects when
540 * sorting strings that already contain SUPERDIGITs, a '\2'
541 * is also prepended, like this
543 * file\21 (file1)
544 * file\25 (file5)
545 * file\2:10 (file10)
546 * file\2:26 (file26)
547 * file\2::100 (file100)
548 * file:foo (file:foo)
550 * This has the side-effect of sorting numbers before everything else (except
551 * dots), but this is probably OK.
553 * Leading digits are ignored when doing the above. To discriminate
554 * numbers which differ only in the number of leading digits, we append
555 * the number of leading digits as a byte at the very end of the collation
556 * key.
558 * To try avoid conflict with any collation key sequence generated by libc we
559 * start each switch to a special cased part with a sentinel that hopefully
560 * will sort before anything libc will generate.
563 if (len < 0)
564 len = strlen (str);
566 result = g_string_sized_new (len * 2);
567 append = g_string_sized_new (0);
569 end = str + len;
571 /* No need to use utf8 functions, since we're only looking for ascii chars */
572 for (prev = p = str; p < end; p++)
574 switch (*p)
576 case '.':
577 if (prev != p)
579 collate_key = g_utf8_collate_key (prev, p - prev);
580 g_string_append (result, collate_key);
581 g_free (collate_key);
584 g_string_append (result, COLLATION_SENTINEL "\1");
586 /* skip the dot */
587 prev = p + 1;
588 break;
590 case '0':
591 case '1':
592 case '2':
593 case '3':
594 case '4':
595 case '5':
596 case '6':
597 case '7':
598 case '8':
599 case '9':
600 if (prev != p)
602 collate_key = g_utf8_collate_key (prev, p - prev);
603 g_string_append (result, collate_key);
604 g_free (collate_key);
607 g_string_append (result, COLLATION_SENTINEL "\2");
609 prev = p;
611 /* write d-1 colons */
612 if (*p == '0')
614 leading_zeros = 1;
615 digits = 0;
617 else
619 leading_zeros = 0;
620 digits = 1;
623 while (++p < end)
625 if (*p == '0' && !digits)
626 ++leading_zeros;
627 else if (g_ascii_isdigit(*p))
628 ++digits;
629 else
631 /* count an all-zero sequence as
632 * one digit plus leading zeros
634 if (!digits)
636 ++digits;
637 --leading_zeros;
639 break;
643 while (digits > 1)
645 g_string_append_c (result, ':');
646 --digits;
649 if (leading_zeros > 0)
651 g_string_append_c (append, (char)leading_zeros);
652 prev += leading_zeros;
655 /* write the number itself */
656 g_string_append_len (result, prev, p - prev);
658 prev = p;
659 --p; /* go one step back to avoid disturbing outer loop */
660 break;
662 default:
663 /* other characters just accumulate */
664 break;
668 if (prev != p)
670 collate_key = g_utf8_collate_key (prev, p - prev);
671 g_string_append (result, collate_key);
672 g_free (collate_key);
675 g_string_append (result, append->str);
676 g_string_free (append, TRUE);
678 return g_string_free (result, FALSE);
679 #else /* HAVE_CARBON */
680 return carbon_collate_key_for_filename (str, len);
681 #endif