1 /* gunicollate.c - Collation
3 * Copyright 2001,2005 Red Hat, Inc.
5 * SPDX-License-Identifier: LGPL-2.1-or-later
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
12 * This library 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 GNU
15 * Lesser General Public License for more details.
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this library; if not, see <http://www.gnu.org/licenses/>.
30 #include <CoreServices/CoreServices.h>
35 #include "gunicodeprivate.h"
37 #include "gstrfuncs.h"
38 #include "gtestutils.h"
42 #if SIZEOF_WCHAR_T == 4 && defined(__STDC_ISO_10646__)
43 #define GUNICHAR_EQUALS_WCHAR_T 1
47 /* Workaround for bug in MSVCR80.DLL */
49 msc_strxfrm_wrapper (char *string1,
53 if (!string1 || count <= 0)
57 return strxfrm (&tmp, string2, 1);
59 return strxfrm (string1, string2, count);
61 #define strxfrm msc_strxfrm_wrapper
66 * @str1: a UTF-8 encoded string
67 * @str2: a UTF-8 encoded string
69 * Compares two strings for ordering using the linguistically
70 * correct rules for the [current locale][setlocale].
71 * When sorting a large number of strings, it will be significantly
72 * faster to obtain collation keys with g_utf8_collate_key() and
73 * compare the keys with strcmp() when sorting instead of sorting
74 * the original strings.
76 * If the two strings are not comparable due to being in different collation
77 * sequences, the result is undefined. This can happen if the strings are in
78 * different language scripts, for example.
80 * Returns: < 0 if @str1 compares before @str2,
81 * 0 if they compare equal, > 0 if @str1 compares after @str2.
84 g_utf8_collate (const gchar *str1,
97 g_return_val_if_fail (str1 != NULL, 0);
98 g_return_val_if_fail (str2 != NULL, 0);
100 str1_utf16 = g_utf8_to_utf16 (str1, -1, NULL, &len1, NULL);
101 str2_utf16 = g_utf8_to_utf16 (str2, -1, NULL, &len2, NULL);
103 UCCompareTextDefault (kUCCollateStandardOptions,
104 str1_utf16, len1, str2_utf16, len2,
111 #elif defined(HAVE_WCHAR_H) && defined(GUNICHAR_EQUALS_WCHAR_T)
116 g_return_val_if_fail (str1 != NULL, 0);
117 g_return_val_if_fail (str2 != NULL, 0);
119 str1_norm = _g_utf8_normalize_wc (str1, -1, G_NORMALIZE_ALL_COMPOSE);
120 str2_norm = _g_utf8_normalize_wc (str2, -1, G_NORMALIZE_ALL_COMPOSE);
122 result = wcscoll ((wchar_t *)str1_norm, (wchar_t *)str2_norm);
129 const gchar *charset;
133 g_return_val_if_fail (str1 != NULL, 0);
134 g_return_val_if_fail (str2 != NULL, 0);
136 str1_norm = g_utf8_normalize (str1, -1, G_NORMALIZE_ALL_COMPOSE);
137 str2_norm = g_utf8_normalize (str2, -1, G_NORMALIZE_ALL_COMPOSE);
139 if (g_get_charset (&charset))
141 result = strcoll (str1_norm, str2_norm);
145 gchar *str1_locale = g_convert (str1_norm, -1, charset, "UTF-8", NULL, NULL, NULL);
146 gchar *str2_locale = g_convert (str2_norm, -1, charset, "UTF-8", NULL, NULL, NULL);
148 if (str1_locale && str2_locale)
149 result = strcoll (str1_locale, str2_locale);
150 else if (str1_locale)
152 else if (str2_locale)
155 result = strcmp (str1_norm, str2_norm);
157 g_free (str1_locale);
158 g_free (str2_locale);
169 #if defined(HAVE_WCHAR_H) && defined(GUNICHAR_EQUALS_WCHAR_T)
170 /* We need UTF-8 encoding of numbers to encode the weights if
171 * we are using wcsxfrm. However, we aren't encoding Unicode
172 * characters, so we can't simply use g_unichar_to_utf8.
174 * The following routine is taken (with modification) from GNU
175 * libc's strxfrm routine:
177 * Copyright (C) 1995-1999,2000,2001 Free Software Foundation, Inc.
178 * Written by Ulrich Drepper <drepper@cygnus.com>, 1995.
181 utf8_encode (char *buf, wchar_t val)
195 for (step = 2; step < 6; ++step)
196 if ((val & (~(guint32)0 << (5 * step + 1))) == 0)
202 *buf = (unsigned char) (~0xff >> step);
206 buf[step] = 0x80 | (val & 0x3f);
221 collate_key_to_string (UCCollationValue *key,
226 long *lkey = (long *) key;
228 /* UCCollationValue format:
230 * UCCollateOptions (32/64 bits)
231 * SizeInBytes (32/64 bits)
232 * Value (8 bits arrey)
234 * UCCollateOptions: ordering option mask of the collator
235 * used to create the key. Size changes on 32-bit / 64-bit
236 * hosts. On 64-bits also the extra half-word seems to have
237 * some extra (unknown) meaning.
238 * SizeInBytes: size of the whole structure, in bytes
239 * (including UCCollateOptions and SizeInBytes fields). Size
240 * changes on 32-bit & 64-bit hosts.
241 * Value: array of bytes containing the comparison weights.
242 * Seems to have several sub-strings separated by \001 and \002
243 * chars. Also, experience shows this is directly strcmp-able.
246 result_len = lkey[1];
247 result = g_malloc (result_len + 1);
248 memcpy (result, &lkey[2], result_len);
249 result[result_len] = '\0';
255 carbon_collate_key_with_collator (const gchar *str,
257 CollatorRef collator)
259 UniChar *str_utf16 = NULL;
262 UCCollationValue staticbuf[512];
263 UCCollationValue *freeme = NULL;
264 UCCollationValue *buf;
268 gchar *result = NULL;
270 str_utf16 = g_utf8_to_utf16 (str, len, NULL, &len_utf16, NULL);
271 try_len = len_utf16 * 5 + 2;
273 if (try_len <= sizeof staticbuf)
276 buf_len = sizeof staticbuf;
280 freeme = g_new (UCCollationValue, try_len);
285 ret = UCGetCollationKey (collator, str_utf16, len_utf16,
286 buf_len, &key_len, buf);
288 if (ret == kCollateBufferTooSmall)
290 freeme = g_renew (UCCollationValue, freeme, try_len * 2);
292 buf_len = try_len * 2;
293 ret = UCGetCollationKey (collator, str_utf16, len_utf16,
294 buf_len, &key_len, buf);
298 result = collate_key_to_string (buf, key_len);
300 result = g_strdup ("");
308 carbon_collate_key (const gchar *str,
311 static CollatorRef collator;
313 if (G_UNLIKELY (!collator))
315 UCCreateCollator (NULL, 0, kUCCollateStandardOptions, &collator);
319 static gboolean been_here;
321 g_warning ("%s: UCCreateCollator failed", G_STRLOC);
323 return g_strdup ("");
327 return carbon_collate_key_with_collator (str, len, collator);
331 carbon_collate_key_for_filename (const gchar *str,
334 static CollatorRef collator;
336 if (G_UNLIKELY (!collator))
338 /* http://developer.apple.com/qa/qa2004/qa1159.html */
339 UCCreateCollator (NULL, 0,
340 kUCCollateComposeInsensitiveMask
341 | kUCCollateWidthInsensitiveMask
342 | kUCCollateCaseInsensitiveMask
343 | kUCCollateDigitsOverrideMask
344 | kUCCollateDigitsAsNumberMask
345 | kUCCollatePunctuationSignificantMask,
350 static gboolean been_here;
352 g_warning ("%s: UCCreateCollator failed", G_STRLOC);
354 return g_strdup ("");
358 return carbon_collate_key_with_collator (str, len, collator);
361 #endif /* HAVE_CARBON */
364 * g_utf8_collate_key:
365 * @str: a UTF-8 encoded string.
366 * @len: length of @str, in bytes, or -1 if @str is nul-terminated.
368 * Converts a string into a collation key that can be compared
369 * with other collation keys produced by the same function using
372 * The results of comparing the collation keys of two strings
373 * with strcmp() will always be the same as comparing the two
374 * original keys with g_utf8_collate().
376 * Note that this function depends on the [current locale][setlocale].
378 * Returns: a newly allocated string. This string should
379 * be freed with g_free() when you are done with it.
382 g_utf8_collate_key (const gchar *str,
389 g_return_val_if_fail (str != NULL, NULL);
390 result = carbon_collate_key (str, len);
392 #elif defined(HAVE_WCHAR_H) && defined(GUNICHAR_EQUALS_WCHAR_T)
398 gsize result_len = 0;
400 g_return_val_if_fail (str != NULL, NULL);
402 str_norm = _g_utf8_normalize_wc (str, len, G_NORMALIZE_ALL_COMPOSE);
404 xfrm_len = wcsxfrm (NULL, (wchar_t *)str_norm, 0);
405 result_wc = g_new (wchar_t, xfrm_len + 1);
406 wcsxfrm (result_wc, (wchar_t *)str_norm, xfrm_len + 1);
408 for (i = 0; i < xfrm_len; i++)
409 result_len += utf8_encode (NULL, result_wc[i]);
411 result = g_malloc (result_len + 1);
413 for (i = 0; i < xfrm_len; i++)
414 result_len += utf8_encode (result + result_len, result_wc[i]);
416 result[result_len] = '\0';
425 const gchar *charset;
428 g_return_val_if_fail (str != NULL, NULL);
430 str_norm = g_utf8_normalize (str, len, G_NORMALIZE_ALL_COMPOSE);
434 if (g_get_charset (&charset))
436 xfrm_len = strxfrm (NULL, str_norm, 0);
437 if (xfrm_len < G_MAXINT - 2)
439 result = g_malloc (xfrm_len + 1);
440 strxfrm (result, str_norm, xfrm_len + 1);
445 gchar *str_locale = g_convert (str_norm, -1, charset, "UTF-8", NULL, NULL, NULL);
449 xfrm_len = strxfrm (NULL, str_locale, 0);
450 if (xfrm_len >= G_MAXINT - 2)
458 result = g_malloc (xfrm_len + 2);
460 strxfrm (result + 1, str_locale, xfrm_len + 1);
468 xfrm_len = strlen (str_norm);
469 result = g_malloc (xfrm_len + 2);
471 memcpy (result + 1, str_norm, xfrm_len);
472 result[xfrm_len+1] = '\0';
481 /* This is a collation key that is very very likely to sort before any
482 * collation key that libc strxfrm generates. We use this before any
483 * special case (dot or number) to make sure that its sorted before
486 #define COLLATION_SENTINEL "\1\1\1"
489 * g_utf8_collate_key_for_filename:
490 * @str: a UTF-8 encoded string.
491 * @len: length of @str, in bytes, or -1 if @str is nul-terminated.
493 * Converts a string into a collation key that can be compared
494 * with other collation keys produced by the same function using strcmp().
496 * In order to sort filenames correctly, this function treats the dot '.'
497 * as a special case. Most dictionary orderings seem to consider it
498 * insignificant, thus producing the ordering "event.c" "eventgenerator.c"
499 * "event.h" instead of "event.c" "event.h" "eventgenerator.c". Also, we
500 * would like to treat numbers intelligently so that "file1" "file10" "file5"
501 * is sorted as "file1" "file5" "file10".
503 * Note that this function depends on the [current locale][setlocale].
505 * Returns: a newly allocated string. This string should
506 * be freed with g_free() when you are done with it.
511 g_utf8_collate_key_for_filename (const gchar *str,
527 * Split the filename into collatable substrings which do
528 * not contain [.0-9] and special-cased substrings. The collatable
529 * substrings are run through the normal g_utf8_collate_key() and the
530 * resulting keys are concatenated with keys generated from the
531 * special-cased substrings.
533 * Special cases: Dots are handled by replacing them with '\1' which
534 * implies that short dot-delimited substrings are before long ones,
541 * Numbers are handled by prepending to each number d-1 superdigits
542 * where d = number of digits in the number and SUPERDIGIT is a
543 * character with an integer value higher than any digit (for instance
544 * ':'). This ensures that single-digit numbers are sorted before
545 * double-digit numbers which in turn are sorted separately from
546 * triple-digit numbers, etc. To avoid strange side-effects when
547 * sorting strings that already contain SUPERDIGITs, a '\2'
548 * is also prepended, like this
554 * file\2::100 (file100)
555 * file:foo (file:foo)
557 * This has the side-effect of sorting numbers before everything else (except
558 * dots), but this is probably OK.
560 * Leading digits are ignored when doing the above. To discriminate
561 * numbers which differ only in the number of leading digits, we append
562 * the number of leading digits as a byte at the very end of the collation
565 * To try avoid conflict with any collation key sequence generated by libc we
566 * start each switch to a special cased part with a sentinel that hopefully
567 * will sort before anything libc will generate.
573 result = g_string_sized_new (len * 2);
574 append = g_string_sized_new (0);
578 /* No need to use utf8 functions, since we're only looking for ascii chars */
579 for (prev = p = str; p < end; p++)
586 collate_key = g_utf8_collate_key (prev, p - prev);
587 g_string_append (result, collate_key);
588 g_free (collate_key);
591 g_string_append (result, COLLATION_SENTINEL "\1");
609 collate_key = g_utf8_collate_key (prev, p - prev);
610 g_string_append (result, collate_key);
611 g_free (collate_key);
614 g_string_append (result, COLLATION_SENTINEL "\2");
618 /* write d-1 colons */
632 if (*p == '0' && !digits)
634 else if (g_ascii_isdigit(*p))
638 /* count an all-zero sequence as
639 * one digit plus leading zeros
652 g_string_append_c (result, ':');
656 if (leading_zeros > 0)
658 g_string_append_c (append, (char)leading_zeros);
659 prev += leading_zeros;
662 /* write the number itself */
663 g_string_append_len (result, prev, p - prev);
666 --p; /* go one step back to avoid disturbing outer loop */
670 /* other characters just accumulate */
677 collate_key = g_utf8_collate_key (prev, p - prev);
678 g_string_append (result, collate_key);
679 g_free (collate_key);
682 g_string_append (result, append->str);
683 g_string_free (append, TRUE);
685 return g_string_free (result, FALSE);
686 #else /* HAVE_CARBON */
687 return carbon_collate_key_for_filename (str, len);