LCOV - code coverage report
Current view: top level - glib - gunicollate.c (source / functions) Coverage Total Hit
Test: unnamed Lines: 100.0 % 96 96
Test Date: 2026-01-13 05:17:23 Functions: 100.0 % 4 4
Branches: - 0 0

             Branch data     Line data    Source code
       1                 :             : /* gunicollate.c - Collation
       2                 :             :  *
       3                 :             :  *  Copyright 2001,2005 Red Hat, Inc.
       4                 :             :  *
       5                 :             :  * SPDX-License-Identifier: LGPL-2.1-or-later
       6                 :             :  *
       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.
      11                 :             :  *
      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.
      16                 :             :  *
      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/>.
      19                 :             :  */
      20                 :             : 
      21                 :             : #include "config.h"
      22                 :             : 
      23                 :             : #include <locale.h>
      24                 :             : #include <string.h>
      25                 :             : #ifdef HAVE_WCHAR_H
      26                 :             : #include <wchar.h>
      27                 :             : #endif
      28                 :             : 
      29                 :             : #ifdef HAVE_CARBON
      30                 :             : #include <CoreServices/CoreServices.h>
      31                 :             : #endif
      32                 :             : 
      33                 :             : #include "gmem.h"
      34                 :             : #include "gunicode.h"
      35                 :             : #include "gunicodeprivate.h"
      36                 :             : #include "gstring.h"
      37                 :             : #include "gstrfuncs.h"
      38                 :             : #include "gtestutils.h"
      39                 :             : #include "gcharset.h"
      40                 :             : #include "gconvert.h"
      41                 :             : 
      42                 :             : #if SIZEOF_WCHAR_T == 4 && defined(__STDC_ISO_10646__)
      43                 :             : #define GUNICHAR_EQUALS_WCHAR_T 1
      44                 :             : #endif
      45                 :             : 
      46                 :             : #ifdef _MSC_VER
      47                 :             : /* Workaround for bug in MSVCR80.DLL */
      48                 :             : static gsize
      49                 :             : msc_strxfrm_wrapper (char       *string1,
      50                 :             :                      const char *string2,
      51                 :             :                      gsize       count)
      52                 :             : {
      53                 :             :   if (!string1 || count <= 0)
      54                 :             :     {
      55                 :             :       char tmp;
      56                 :             : 
      57                 :             :       return strxfrm (&tmp, string2, 1);
      58                 :             :     }
      59                 :             :   return strxfrm (string1, string2, count);
      60                 :             : }
      61                 :             : #define strxfrm msc_strxfrm_wrapper
      62                 :             : #endif
      63                 :             : 
      64                 :             : /**
      65                 :             :  * g_utf8_collate:
      66                 :             :  * @str1: a UTF-8 encoded string
      67                 :             :  * @str2: a UTF-8 encoded string
      68                 :             :  * 
      69                 :             :  * Compares two strings for ordering using the linguistically
      70                 :             :  * correct rules for the [current locale](running.html#locale).
      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.
      75                 :             :  * 
      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.
      79                 :             :  *
      80                 :             :  * Returns: < 0 if @str1 compares before @str2, 
      81                 :             :  *   0 if they compare equal, > 0 if @str1 compares after @str2.
      82                 :             :  **/
      83                 :             : gint
      84                 :          89 : g_utf8_collate (const gchar *str1,
      85                 :             :                 const gchar *str2)
      86                 :             : {
      87                 :             :   gint result;
      88                 :             : 
      89                 :             : #ifdef HAVE_CARBON
      90                 :             : 
      91                 :             :   UniChar *str1_utf16;
      92                 :             :   UniChar *str2_utf16;
      93                 :             :   glong len1;
      94                 :             :   glong len2;
      95                 :             :   SInt32 retval = 0;
      96                 :             : 
      97                 :             :   g_return_val_if_fail (str1 != NULL, 0);
      98                 :             :   g_return_val_if_fail (str2 != NULL, 0);
      99                 :             : 
     100                 :             :   str1_utf16 = g_utf8_to_utf16 (str1, -1, NULL, &len1, NULL);
     101                 :             :   str2_utf16 = g_utf8_to_utf16 (str2, -1, NULL, &len2, NULL);
     102                 :             : 
     103                 :             :   UCCompareTextDefault (kUCCollateStandardOptions,
     104                 :             :                         str1_utf16, len1, str2_utf16, len2,
     105                 :             :                         NULL, &retval);
     106                 :             :   result = retval;
     107                 :             : 
     108                 :             :   g_free (str2_utf16);
     109                 :             :   g_free (str1_utf16);
     110                 :             : 
     111                 :             : #elif defined(HAVE_WCHAR_H) && defined(GUNICHAR_EQUALS_WCHAR_T)
     112                 :             : 
     113                 :             :   gunichar *str1_norm;
     114                 :             :   gunichar *str2_norm;
     115                 :             : 
     116                 :          89 :   g_return_val_if_fail (str1 != NULL, 0);
     117                 :          89 :   g_return_val_if_fail (str2 != NULL, 0);
     118                 :             : 
     119                 :          89 :   str1_norm = _g_utf8_normalize_wc (str1, -1, G_NORMALIZE_ALL_COMPOSE);
     120                 :          89 :   str2_norm = _g_utf8_normalize_wc (str2, -1, G_NORMALIZE_ALL_COMPOSE);
     121                 :             : 
     122                 :          89 :   result = wcscoll ((wchar_t *)str1_norm, (wchar_t *)str2_norm);
     123                 :             : 
     124                 :          89 :   g_free (str1_norm);
     125                 :          89 :   g_free (str2_norm);
     126                 :             : 
     127                 :             : #else
     128                 :             : 
     129                 :             :   const gchar *charset;
     130                 :             :   gchar *str1_norm;
     131                 :             :   gchar *str2_norm;
     132                 :             : 
     133                 :             :   g_return_val_if_fail (str1 != NULL, 0);
     134                 :             :   g_return_val_if_fail (str2 != NULL, 0);
     135                 :             : 
     136                 :             :   str1_norm = g_utf8_normalize (str1, -1, G_NORMALIZE_ALL_COMPOSE);
     137                 :             :   str2_norm = g_utf8_normalize (str2, -1, G_NORMALIZE_ALL_COMPOSE);
     138                 :             : 
     139                 :             :   if (g_get_charset (&charset))
     140                 :             :     {
     141                 :             :       result = strcoll (str1_norm, str2_norm);
     142                 :             :     }
     143                 :             :   else
     144                 :             :     {
     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);
     147                 :             : 
     148                 :             :       if (str1_locale && str2_locale)
     149                 :             :         result =  strcoll (str1_locale, str2_locale);
     150                 :             :       else if (str1_locale)
     151                 :             :         result = -1;
     152                 :             :       else if (str2_locale)
     153                 :             :         result = 1;
     154                 :             :       else
     155                 :             :         result = strcmp (str1_norm, str2_norm);
     156                 :             : 
     157                 :             :       g_free (str1_locale);
     158                 :             :       g_free (str2_locale);
     159                 :             :     }
     160                 :             : 
     161                 :             :   g_free (str1_norm);
     162                 :             :   g_free (str2_norm);
     163                 :             : 
     164                 :             : #endif
     165                 :             : 
     166                 :          89 :   return result;
     167                 :             : }
     168                 :             : 
     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.
     173                 :             :  *
     174                 :             :  * The following routine is taken (with modification) from GNU
     175                 :             :  * libc's strxfrm routine:
     176                 :             :  *
     177                 :             :  * Copyright (C) 1995-1999,2000,2001 Free Software Foundation, Inc.
     178                 :             :  * Written by Ulrich Drepper <drepper@cygnus.com>, 1995.
     179                 :             :  */
     180                 :             : static inline int
     181                 :        9072 : utf8_encode (char *buf, wchar_t val)
     182                 :             : {
     183                 :             :   int retval;
     184                 :             : 
     185                 :        9072 :   if (val < 0x80)
     186                 :             :     {
     187                 :        7956 :       if (buf)
     188                 :        3978 :         *buf++ = (char) val;
     189                 :        7956 :       retval = 1;
     190                 :             :     }
     191                 :             :   else
     192                 :             :     {
     193                 :             :       int step;
     194                 :             : 
     195                 :        1152 :       for (step = 2; step < 6; ++step)
     196                 :        1152 :         if ((val & (~(guint32)0 << (5 * step + 1))) == 0)
     197                 :        1116 :           break;
     198                 :        1116 :       retval = step;
     199                 :             : 
     200                 :        1116 :       if (buf)
     201                 :             :         {
     202                 :         558 :           *buf = (unsigned char) (~0xff >> step);
     203                 :         558 :           --step;
     204                 :             :           do
     205                 :             :             {
     206                 :         576 :               buf[step] = 0x80 | (val & 0x3f);
     207                 :         576 :               val >>= 6;
     208                 :             :             }
     209                 :         576 :           while (--step > 0);
     210                 :         558 :           *buf |= val;
     211                 :             :         }
     212                 :             :     }
     213                 :             : 
     214                 :        9072 :   return retval;
     215                 :             : }
     216                 :             : #endif
     217                 :             : 
     218                 :             : #ifdef HAVE_CARBON
     219                 :             : 
     220                 :             : static gchar *
     221                 :             : collate_key_to_string (UCCollationValue *key,
     222                 :             :                        gsize             key_len)
     223                 :             : {
     224                 :             :   gchar *result;
     225                 :             :   gsize result_len;
     226                 :             :   long *lkey = (long *) key;
     227                 :             : 
     228                 :             :   /* UCCollationValue format:
     229                 :             :    *
     230                 :             :    * UCCollateOptions (32/64 bits)
     231                 :             :    * SizeInBytes      (32/64 bits)
     232                 :             :    * Value            (8 bits array)
     233                 :             :    *
     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.
     244                 :             :    */
     245                 :             : 
     246                 :             :   result_len = lkey[1];
     247                 :             :   result = g_malloc (result_len + 1);
     248                 :             :   memcpy (result, &lkey[2], result_len);
     249                 :             :   result[result_len] = '\0';
     250                 :             : 
     251                 :             :   return result;
     252                 :             : }
     253                 :             : 
     254                 :             : static gchar *
     255                 :             : carbon_collate_key_with_collator (const gchar *str,
     256                 :             :                                   gssize       len,
     257                 :             :                                   CollatorRef  collator)
     258                 :             : {
     259                 :             :   UniChar *str_utf16 = NULL;
     260                 :             :   glong len_utf16;
     261                 :             :   OSStatus ret;
     262                 :             :   UCCollationValue staticbuf[512];
     263                 :             :   UCCollationValue *freeme = NULL;
     264                 :             :   UCCollationValue *buf;
     265                 :             :   ItemCount buf_len;
     266                 :             :   ItemCount key_len;
     267                 :             :   ItemCount try_len;
     268                 :             :   gchar *result = NULL;
     269                 :             : 
     270                 :             :   str_utf16 = g_utf8_to_utf16 (str, len, NULL, &len_utf16, NULL);
     271                 :             :   try_len = len_utf16 * 5 + 2;
     272                 :             : 
     273                 :             :   if (try_len <= sizeof staticbuf)
     274                 :             :     {
     275                 :             :       buf = staticbuf;
     276                 :             :       buf_len = sizeof staticbuf;
     277                 :             :     }
     278                 :             :   else
     279                 :             :     {
     280                 :             :       freeme = g_new (UCCollationValue, try_len);
     281                 :             :       buf = freeme;
     282                 :             :       buf_len = try_len;
     283                 :             :     }
     284                 :             : 
     285                 :             :   ret = UCGetCollationKey (collator, str_utf16, len_utf16,
     286                 :             :                            buf_len, &key_len, buf);
     287                 :             : 
     288                 :             :   if (ret == kCollateBufferTooSmall)
     289                 :             :     {
     290                 :             :       freeme = g_renew (UCCollationValue, freeme, try_len * 2);
     291                 :             :       buf = freeme;
     292                 :             :       buf_len = try_len * 2;
     293                 :             :       ret = UCGetCollationKey (collator, str_utf16, len_utf16,
     294                 :             :                                buf_len, &key_len, buf);
     295                 :             :     }
     296                 :             : 
     297                 :             :   if (ret == 0)
     298                 :             :     result = collate_key_to_string (buf, key_len);
     299                 :             :   else
     300                 :             :     result = g_strdup ("");
     301                 :             : 
     302                 :             :   g_free (freeme);
     303                 :             :   g_free (str_utf16);
     304                 :             :   return result;
     305                 :             : }
     306                 :             : 
     307                 :             : static gchar *
     308                 :             : carbon_collate_key (const gchar *str,
     309                 :             :                     gssize       len)
     310                 :             : {
     311                 :             :   static CollatorRef collator;
     312                 :             : 
     313                 :             :   if (G_UNLIKELY (!collator))
     314                 :             :     {
     315                 :             :       UCCreateCollator (NULL, 0, kUCCollateStandardOptions, &collator);
     316                 :             : 
     317                 :             :       if (!collator)
     318                 :             :         {
     319                 :             :           static gboolean been_here;
     320                 :             :           if (!been_here)
     321                 :             :             g_warning ("%s: UCCreateCollator failed", G_STRLOC);
     322                 :             :           been_here = TRUE;
     323                 :             :           return g_strdup ("");
     324                 :             :         }
     325                 :             :     }
     326                 :             : 
     327                 :             :   return carbon_collate_key_with_collator (str, len, collator);
     328                 :             : }
     329                 :             : 
     330                 :             : static gchar *
     331                 :             : carbon_collate_key_for_filename (const gchar *str,
     332                 :             :                                  gssize       len)
     333                 :             : {
     334                 :             :   static CollatorRef collator;
     335                 :             : 
     336                 :             :   if (G_UNLIKELY (!collator))
     337                 :             :     {
     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, 
     346                 :             :                         &collator);
     347                 :             : 
     348                 :             :       if (!collator)
     349                 :             :         {
     350                 :             :           static gboolean been_here;
     351                 :             :           if (!been_here)
     352                 :             :             g_warning ("%s: UCCreateCollator failed", G_STRLOC);
     353                 :             :           been_here = TRUE;
     354                 :             :           return g_strdup ("");
     355                 :             :         }
     356                 :             :     }
     357                 :             : 
     358                 :             :   return carbon_collate_key_with_collator (str, len, collator);
     359                 :             : }
     360                 :             : 
     361                 :             : #endif /* HAVE_CARBON */
     362                 :             : 
     363                 :             : /**
     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.
     367                 :             :  *
     368                 :             :  * Converts a string into a collation key that can be compared
     369                 :             :  * with other collation keys produced by the same function using 
     370                 :             :  * strcmp(). 
     371                 :             :  *
     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().
     375                 :             :  *
     376                 :             :  * Note that this function depends on the [current locale](running.html#locale).
     377                 :             :  *
     378                 :             :  * Note that the returned string is not guaranteed to be in any
     379                 :             :  * encoding, especially UTF-8. The returned value is meant to be
     380                 :             :  * used only for comparisons.
     381                 :             :  * 
     382                 :             :  * Returns: (transfer full) (type filename): a newly allocated string.
     383                 :             :  *   The contents of the string are only meant to be used when sorting.
     384                 :             :  *   This string should be freed with g_free() when you are done with it.
     385                 :             :  **/
     386                 :             : gchar *
     387                 :         299 : g_utf8_collate_key (const gchar *str,
     388                 :             :                     gssize       len)
     389                 :             : {
     390                 :             :   gchar *result;
     391                 :             : 
     392                 :             : #ifdef HAVE_CARBON
     393                 :             : 
     394                 :             :   g_return_val_if_fail (str != NULL, NULL);
     395                 :             :   result = carbon_collate_key (str, len);
     396                 :             : 
     397                 :             : #elif defined(HAVE_WCHAR_H) && defined(GUNICHAR_EQUALS_WCHAR_T)
     398                 :             : 
     399                 :             :   gsize xfrm_len;
     400                 :             :   gunichar *str_norm;
     401                 :             :   wchar_t *result_wc;
     402                 :             :   gsize i;
     403                 :         299 :   gsize result_len = 0;
     404                 :             : 
     405                 :         299 :   g_return_val_if_fail (str != NULL, NULL);
     406                 :             : 
     407                 :         299 :   str_norm = _g_utf8_normalize_wc (str, len, G_NORMALIZE_ALL_COMPOSE);
     408                 :             : 
     409                 :         299 :   g_return_val_if_fail (str_norm != NULL, NULL);
     410                 :             : 
     411                 :         299 :   xfrm_len = wcsxfrm (NULL, (wchar_t *)str_norm, 0);
     412                 :         299 :   result_wc = g_new (wchar_t, xfrm_len + 1);
     413                 :         299 :   wcsxfrm (result_wc, (wchar_t *)str_norm, xfrm_len + 1);
     414                 :             : 
     415                 :        4835 :   for (i = 0; i < xfrm_len; i++)
     416                 :        4536 :     result_len += utf8_encode (NULL, result_wc[i]);
     417                 :             : 
     418                 :         299 :   result = g_malloc (result_len + 1);
     419                 :         299 :   result_len = 0;
     420                 :        4835 :   for (i = 0; i < xfrm_len; i++)
     421                 :        4536 :     result_len += utf8_encode (result + result_len, result_wc[i]);
     422                 :             : 
     423                 :         299 :   result[result_len] = '\0';
     424                 :             : 
     425                 :         299 :   g_free (result_wc);
     426                 :         299 :   g_free (str_norm);
     427                 :             : 
     428                 :         299 :   return result;
     429                 :             : #else
     430                 :             : 
     431                 :             :   gsize xfrm_len = 0;
     432                 :             :   const gchar *charset;
     433                 :             :   gchar *str_norm;
     434                 :             : 
     435                 :             :   g_return_val_if_fail (str != NULL, NULL);
     436                 :             : 
     437                 :             :   str_norm = g_utf8_normalize (str, len, G_NORMALIZE_ALL_COMPOSE);
     438                 :             : 
     439                 :             :   result = NULL;
     440                 :             : 
     441                 :             :   if (g_get_charset (&charset))
     442                 :             :     {
     443                 :             :       xfrm_len = strxfrm (NULL, str_norm, 0);
     444                 :             :       if (xfrm_len < G_MAXINT - 2)
     445                 :             :         {
     446                 :             :           result = g_malloc (xfrm_len + 1);
     447                 :             :           strxfrm (result, str_norm, xfrm_len + 1);
     448                 :             :         }
     449                 :             :     }
     450                 :             :   else
     451                 :             :     {
     452                 :             :       gchar *str_locale = g_convert (str_norm, -1, charset, "UTF-8", NULL, NULL, NULL);
     453                 :             : 
     454                 :             :       if (str_locale)
     455                 :             :         {
     456                 :             :           xfrm_len = strxfrm (NULL, str_locale, 0);
     457                 :             :           if (xfrm_len >= G_MAXINT - 2)
     458                 :             :             {
     459                 :             :               g_free (str_locale);
     460                 :             :               str_locale = NULL;
     461                 :             :             }
     462                 :             :         }
     463                 :             :       if (str_locale)
     464                 :             :         {
     465                 :             :           result = g_malloc (xfrm_len + 2);
     466                 :             :           result[0] = 'A';
     467                 :             :           strxfrm (result + 1, str_locale, xfrm_len + 1);
     468                 :             :           
     469                 :             :           g_free (str_locale);
     470                 :             :         }
     471                 :             :     }
     472                 :             :     
     473                 :             :   if (!result) 
     474                 :             :     {
     475                 :             :       xfrm_len = strlen (str_norm);
     476                 :             :       result = g_malloc (xfrm_len + 2);
     477                 :             :       result[0] = 'B';
     478                 :             :       memcpy (result + 1, str_norm, xfrm_len);
     479                 :             :       result[xfrm_len+1] = '\0';
     480                 :             :     }
     481                 :             : 
     482                 :             :   g_free (str_norm);
     483                 :             : #endif
     484                 :             : 
     485                 :             :   return result;
     486                 :             : }
     487                 :             : 
     488                 :             : /* This is a collation key that is very very likely to sort before any
     489                 :             :  * collation key that libc strxfrm generates. We use this before any
     490                 :             :  * special case (dot or number) to make sure that its sorted before
     491                 :             :  * anything else.
     492                 :             :  */
     493                 :             : #define COLLATION_SENTINEL "\1\1\1"
     494                 :             : 
     495                 :             : /**
     496                 :             :  * g_utf8_collate_key_for_filename:
     497                 :             :  * @str: a UTF-8 encoded string.
     498                 :             :  * @len: length of @str, in bytes, or -1 if @str is nul-terminated.
     499                 :             :  *
     500                 :             :  * Converts a string into a collation key that can be compared
     501                 :             :  * with other collation keys produced by the same function using strcmp(). 
     502                 :             :  * 
     503                 :             :  * In order to sort filenames correctly, this function treats the dot '.' 
     504                 :             :  * as a special case. Most dictionary orderings seem to consider it
     505                 :             :  * insignificant, thus producing the ordering "event.c" "eventgenerator.c"
     506                 :             :  * "event.h" instead of "event.c" "event.h" "eventgenerator.c". Also, we
     507                 :             :  * would like to treat numbers intelligently so that "file1" "file10" "file5"
     508                 :             :  * is sorted as "file1" "file5" "file10".
     509                 :             :  * 
     510                 :             :  * Note that this function depends on the [current locale](running.html#locale).
     511                 :             :  *
     512                 :             :  * Note that the returned string is not guaranteed to be in any
     513                 :             :  * encoding, especially UTF-8. The returned value is meant to be
     514                 :             :  * used only for comparisons.
     515                 :             :  *
     516                 :             :  * Returns: (transfer full) (type filename): a newly allocated string.
     517                 :             :  *   The contents of the string are only meant to be used when sorting.
     518                 :             :  *   This string should be freed with g_free() when you are done with it.
     519                 :             :  *
     520                 :             :  * Since: 2.8
     521                 :             :  */
     522                 :             : gchar *
     523                 :          36 : g_utf8_collate_key_for_filename (const gchar *str,
     524                 :             :                                  gssize       len)
     525                 :             : {
     526                 :             : #ifndef HAVE_CARBON
     527                 :             :   GString *result;
     528                 :             :   GString *append;
     529                 :             :   const gchar *p;
     530                 :             :   const gchar *prev;
     531                 :             :   const gchar *end;
     532                 :             :   gchar *collate_key;
     533                 :             :   gint digits;
     534                 :             :   gint leading_zeros;
     535                 :             : 
     536                 :             :   /*
     537                 :             :    * How it works:
     538                 :             :    *
     539                 :             :    * Split the filename into collatable substrings which do
     540                 :             :    * not contain [.0-9] and special-cased substrings. The collatable 
     541                 :             :    * substrings are run through the normal g_utf8_collate_key() and the 
     542                 :             :    * resulting keys are concatenated with keys generated from the 
     543                 :             :    * special-cased substrings.
     544                 :             :    *
     545                 :             :    * Special cases: Dots are handled by replacing them with '\1' which 
     546                 :             :    * implies that short dot-delimited substrings are before long ones, 
     547                 :             :    * e.g.
     548                 :             :    * 
     549                 :             :    *   a\1a   (a.a)
     550                 :             :    *   a-\1a  (a-.a)
     551                 :             :    *   aa\1a  (aa.a)
     552                 :             :    * 
     553                 :             :    * Numbers are handled by prepending to each number d-1 superdigits 
     554                 :             :    * where d = number of digits in the number and SUPERDIGIT is a 
     555                 :             :    * character with an integer value higher than any digit (for instance 
     556                 :             :    * ':'). This ensures that single-digit numbers are sorted before 
     557                 :             :    * double-digit numbers which in turn are sorted separately from 
     558                 :             :    * triple-digit numbers, etc. To avoid strange side-effects when 
     559                 :             :    * sorting strings that already contain SUPERDIGITs, a '\2'
     560                 :             :    * is also prepended, like this
     561                 :             :    *
     562                 :             :    *   file\21      (file1)
     563                 :             :    *   file\25      (file5)
     564                 :             :    *   file\2:10    (file10)
     565                 :             :    *   file\2:26    (file26)
     566                 :             :    *   file\2::100  (file100)
     567                 :             :    *   file:foo     (file:foo)
     568                 :             :    * 
     569                 :             :    * This has the side-effect of sorting numbers before everything else (except
     570                 :             :    * dots), but this is probably OK.
     571                 :             :    *
     572                 :             :    * Leading digits are ignored when doing the above. To discriminate
     573                 :             :    * numbers which differ only in the number of leading digits, we append
     574                 :             :    * the number of leading digits as a byte at the very end of the collation
     575                 :             :    * key.
     576                 :             :    *
     577                 :             :    * To try avoid conflict with any collation key sequence generated by libc we
     578                 :             :    * start each switch to a special cased part with a sentinel that hopefully
     579                 :             :    * will sort before anything libc will generate.
     580                 :             :    */
     581                 :             : 
     582                 :          36 :   if (len < 0)
     583                 :          36 :     len = strlen (str);
     584                 :             : 
     585                 :          36 :   result = g_string_sized_new (len * 2);
     586                 :          36 :   append = g_string_sized_new (0);
     587                 :             : 
     588                 :          36 :   end = str + len;
     589                 :             : 
     590                 :             :   /* No need to use utf8 functions, since we're only looking for ascii chars */
     591                 :         216 :   for (prev = p = str; p < end; p++)
     592                 :             :     {
     593                 :         180 :       switch (*p)
     594                 :             :         {
     595                 :          10 :         case '.':
     596                 :          10 :           if (prev != p) 
     597                 :             :             {
     598                 :           8 :               collate_key = g_utf8_collate_key (prev, p - prev);
     599                 :             :               g_string_append (result, collate_key);
     600                 :           8 :               g_free (collate_key);
     601                 :             :             }
     602                 :             :           
     603                 :          10 :           g_string_append (result, COLLATION_SENTINEL "\1");
     604                 :             :           
     605                 :             :           /* skip the dot */
     606                 :          10 :           prev = p + 1;
     607                 :          10 :           break;
     608                 :             :           
     609                 :          20 :         case '0':
     610                 :             :         case '1':
     611                 :             :         case '2':
     612                 :             :         case '3':
     613                 :             :         case '4':
     614                 :             :         case '5':
     615                 :             :         case '6':
     616                 :             :         case '7':
     617                 :             :         case '8':
     618                 :             :         case '9':
     619                 :          20 :           if (prev != p) 
     620                 :             :             {
     621                 :          19 :               collate_key = g_utf8_collate_key (prev, p - prev);
     622                 :             :               g_string_append (result, collate_key);
     623                 :          19 :               g_free (collate_key);
     624                 :             :             }
     625                 :             :           
     626                 :          20 :           g_string_append (result, COLLATION_SENTINEL "\2");
     627                 :             :           
     628                 :          20 :           prev = p;
     629                 :             :           
     630                 :             :           /* write d-1 colons */
     631                 :          20 :           if (*p == '0')
     632                 :             :             {
     633                 :           7 :               leading_zeros = 1;
     634                 :           7 :               digits = 0;
     635                 :             :             }
     636                 :             :           else
     637                 :             :             {
     638                 :          13 :               leading_zeros = 0;
     639                 :          13 :               digits = 1;
     640                 :             :             }
     641                 :             :           
     642                 :          44 :           while (++p < end)
     643                 :             :             {
     644                 :          27 :               if (*p == '0' && !digits)
     645                 :           7 :                 ++leading_zeros;
     646                 :          20 :               else if (g_ascii_isdigit(*p))
     647                 :          17 :                 ++digits;
     648                 :             :               else
     649                 :             :                 {
     650                 :             :                   /* count an all-zero sequence as
     651                 :             :                    * one digit plus leading zeros
     652                 :             :                    */
     653                 :           3 :                   if (!digits)
     654                 :             :                     {
     655                 :           1 :                       ++digits;
     656                 :           1 :                       --leading_zeros;
     657                 :             :                     }        
     658                 :           3 :                   break;
     659                 :             :                 }
     660                 :             :             }
     661                 :             : 
     662                 :          32 :           while (digits > 1)
     663                 :             :             {
     664                 :             :               g_string_append_c (result, ':');
     665                 :          12 :               --digits;
     666                 :             :             }
     667                 :             : 
     668                 :          20 :           if (leading_zeros > 0)
     669                 :             :             {
     670                 :           7 :               g_string_append_c (append, (char)leading_zeros);
     671                 :           7 :               prev += leading_zeros;
     672                 :             :             }
     673                 :             :           
     674                 :             :           /* write the number itself */
     675                 :          20 :           g_string_append_len (result, prev, p - prev);
     676                 :             :           
     677                 :          20 :           prev = p;
     678                 :          20 :           --p;    /* go one step back to avoid disturbing outer loop */
     679                 :          20 :           break;
     680                 :             :           
     681                 :         150 :         default:
     682                 :             :           /* other characters just accumulate */
     683                 :         150 :           break;
     684                 :             :         }
     685                 :             :     }
     686                 :             :   
     687                 :          36 :   if (prev != p) 
     688                 :             :     {
     689                 :          19 :       collate_key = g_utf8_collate_key (prev, p - prev);
     690                 :             :       g_string_append (result, collate_key);
     691                 :          19 :       g_free (collate_key);
     692                 :             :     }
     693                 :             :   
     694                 :          36 :   g_string_append (result, append->str);
     695                 :          36 :   g_string_free (append, TRUE);
     696                 :             : 
     697                 :          36 :   return g_string_free (result, FALSE);
     698                 :             : #else /* HAVE_CARBON */
     699                 :             :   return carbon_collate_key_for_filename (str, len);
     700                 :             : #endif
     701                 :             : }
        

Generated by: LCOV version 2.0-1