Blame glib/gunicollate.c

Packit ae235b
/* gunicollate.c - Collation
Packit ae235b
 *
Packit ae235b
 *  Copyright 2001,2005 Red Hat, Inc.
Packit ae235b
 *
Packit ae235b
 * This library is free software; you can redistribute it and/or
Packit ae235b
 * modify it under the terms of the GNU Lesser General Public
Packit ae235b
 * License as published by the Free Software Foundation; either
Packit ae235b
 * version 2.1 of the License, or (at your option) any later version.
Packit ae235b
 *
Packit ae235b
 * This library is distributed in the hope that it will be useful,
Packit ae235b
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit ae235b
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit ae235b
 * Lesser General Public License for more details.
Packit ae235b
 *
Packit ae235b
 * You should have received a copy of the GNU Lesser General Public License
Packit ae235b
 * along with this library; if not, see <http://www.gnu.org/licenses/>.
Packit ae235b
 */
Packit ae235b
Packit ae235b
#include "config.h"
Packit ae235b
Packit ae235b
#include <locale.h>
Packit ae235b
#include <string.h>
Packit ae235b
#ifdef __STDC_ISO_10646__
Packit ae235b
#include <wchar.h>
Packit ae235b
#endif
Packit ae235b
Packit ae235b
#ifdef HAVE_CARBON
Packit ae235b
#include <CoreServices/CoreServices.h>
Packit ae235b
#endif
Packit ae235b
Packit ae235b
#include "gmem.h"
Packit ae235b
#include "gunicode.h"
Packit ae235b
#include "gunicodeprivate.h"
Packit ae235b
#include "gstring.h"
Packit ae235b
#include "gstrfuncs.h"
Packit ae235b
#include "gtestutils.h"
Packit ae235b
#include "gcharset.h"
Packit ae235b
#ifndef __STDC_ISO_10646__
Packit ae235b
#include "gconvert.h"
Packit ae235b
#endif
Packit ae235b
Packit ae235b
Packit ae235b
#ifdef _MSC_VER
Packit ae235b
/* Workaround for bug in MSVCR80.DLL */
Packit ae235b
static gsize
Packit ae235b
msc_strxfrm_wrapper (char       *string1,
Packit ae235b
		     const char *string2,
Packit ae235b
		     gsize       count)
Packit ae235b
{
Packit ae235b
  if (!string1 || count <= 0)
Packit ae235b
    {
Packit ae235b
      char tmp;
Packit ae235b
Packit ae235b
      return strxfrm (&tmp, string2, 1);
Packit ae235b
    }
Packit ae235b
  return strxfrm (string1, string2, count);
Packit ae235b
}
Packit ae235b
#define strxfrm msc_strxfrm_wrapper
Packit ae235b
#endif
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_utf8_collate:
Packit ae235b
 * @str1: a UTF-8 encoded string
Packit ae235b
 * @str2: a UTF-8 encoded string
Packit ae235b
 * 
Packit ae235b
 * Compares two strings for ordering using the linguistically
Packit ae235b
 * correct rules for the [current locale][setlocale].
Packit ae235b
 * When sorting a large number of strings, it will be significantly 
Packit ae235b
 * faster to obtain collation keys with g_utf8_collate_key() and 
Packit ae235b
 * compare the keys with strcmp() when sorting instead of sorting 
Packit ae235b
 * the original strings.
Packit ae235b
 * 
Packit ae235b
 * Returns: < 0 if @str1 compares before @str2, 
Packit ae235b
 *   0 if they compare equal, > 0 if @str1 compares after @str2.
Packit ae235b
 **/
Packit ae235b
gint
Packit ae235b
g_utf8_collate (const gchar *str1,
Packit ae235b
		const gchar *str2)
Packit ae235b
{
Packit ae235b
  gint result;
Packit ae235b
Packit ae235b
#ifdef HAVE_CARBON
Packit ae235b
Packit ae235b
  UniChar *str1_utf16;
Packit ae235b
  UniChar *str2_utf16;
Packit ae235b
  glong len1;
Packit ae235b
  glong len2;
Packit ae235b
  SInt32 retval = 0;
Packit ae235b
Packit ae235b
  g_return_val_if_fail (str1 != NULL, 0);
Packit ae235b
  g_return_val_if_fail (str2 != NULL, 0);
Packit ae235b
Packit ae235b
  str1_utf16 = g_utf8_to_utf16 (str1, -1, NULL, &len1, NULL);
Packit ae235b
  str2_utf16 = g_utf8_to_utf16 (str2, -1, NULL, &len2, NULL);
Packit ae235b
Packit ae235b
  UCCompareTextDefault (kUCCollateStandardOptions,
Packit ae235b
                        str1_utf16, len1, str2_utf16, len2,
Packit ae235b
                        NULL, &retval);
Packit ae235b
  result = retval;
Packit ae235b
Packit ae235b
  g_free (str2_utf16);
Packit ae235b
  g_free (str1_utf16);
Packit ae235b
Packit ae235b
#elif defined(__STDC_ISO_10646__)
Packit ae235b
Packit ae235b
  gunichar *str1_norm;
Packit ae235b
  gunichar *str2_norm;
Packit ae235b
Packit ae235b
  g_return_val_if_fail (str1 != NULL, 0);
Packit ae235b
  g_return_val_if_fail (str2 != NULL, 0);
Packit ae235b
Packit ae235b
  str1_norm = _g_utf8_normalize_wc (str1, -1, G_NORMALIZE_ALL_COMPOSE);
Packit ae235b
  str2_norm = _g_utf8_normalize_wc (str2, -1, G_NORMALIZE_ALL_COMPOSE);
Packit ae235b
Packit ae235b
  result = wcscoll ((wchar_t *)str1_norm, (wchar_t *)str2_norm);
Packit ae235b
Packit ae235b
  g_free (str1_norm);
Packit ae235b
  g_free (str2_norm);
Packit ae235b
Packit ae235b
#else /* !__STDC_ISO_10646__ */
Packit ae235b
Packit ae235b
  const gchar *charset;
Packit ae235b
  gchar *str1_norm;
Packit ae235b
  gchar *str2_norm;
Packit ae235b
Packit ae235b
  g_return_val_if_fail (str1 != NULL, 0);
Packit ae235b
  g_return_val_if_fail (str2 != NULL, 0);
Packit ae235b
Packit ae235b
  str1_norm = g_utf8_normalize (str1, -1, G_NORMALIZE_ALL_COMPOSE);
Packit ae235b
  str2_norm = g_utf8_normalize (str2, -1, G_NORMALIZE_ALL_COMPOSE);
Packit ae235b
Packit ae235b
  if (g_get_charset (&charset))
Packit ae235b
    {
Packit ae235b
      result = strcoll (str1_norm, str2_norm);
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    {
Packit ae235b
      gchar *str1_locale = g_convert (str1_norm, -1, charset, "UTF-8", NULL, NULL, NULL);
Packit ae235b
      gchar *str2_locale = g_convert (str2_norm, -1, charset, "UTF-8", NULL, NULL, NULL);
Packit ae235b
Packit ae235b
      if (str1_locale && str2_locale)
Packit ae235b
	result =  strcoll (str1_locale, str2_locale);
Packit ae235b
      else if (str1_locale)
Packit ae235b
	result = -1;
Packit ae235b
      else if (str2_locale)
Packit ae235b
	result = 1;
Packit ae235b
      else
Packit ae235b
	result = strcmp (str1_norm, str2_norm);
Packit ae235b
Packit ae235b
      g_free (str1_locale);
Packit ae235b
      g_free (str2_locale);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  g_free (str1_norm);
Packit ae235b
  g_free (str2_norm);
Packit ae235b
Packit ae235b
#endif /* __STDC_ISO_10646__ */
Packit ae235b
Packit ae235b
  return result;
Packit ae235b
}
Packit ae235b
Packit ae235b
#if defined(__STDC_ISO_10646__)
Packit ae235b
/* We need UTF-8 encoding of numbers to encode the weights if
Packit ae235b
 * we are using wcsxfrm. However, we aren't encoding Unicode
Packit ae235b
 * characters, so we can't simply use g_unichar_to_utf8.
Packit ae235b
 *
Packit ae235b
 * The following routine is taken (with modification) from GNU
Packit ae235b
 * libc's strxfrm routine:
Packit ae235b
 *
Packit ae235b
 * Copyright (C) 1995-1999,2000,2001 Free Software Foundation, Inc.
Packit ae235b
 * Written by Ulrich Drepper <drepper@cygnus.com>, 1995.
Packit ae235b
 */
Packit ae235b
static inline int
Packit ae235b
utf8_encode (char *buf, wchar_t val)
Packit ae235b
{
Packit ae235b
  int retval;
Packit ae235b
Packit ae235b
  if (val < 0x80)
Packit ae235b
    {
Packit ae235b
      if (buf)
Packit ae235b
	*buf++ = (char) val;
Packit ae235b
      retval = 1;
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    {
Packit ae235b
      int step;
Packit ae235b
Packit ae235b
      for (step = 2; step < 6; ++step)
Packit ae235b
        if ((val & (~(guint32)0 << (5 * step + 1))) == 0)
Packit ae235b
          break;
Packit ae235b
      retval = step;
Packit ae235b
Packit ae235b
      if (buf)
Packit ae235b
	{
Packit ae235b
	  *buf = (unsigned char) (~0xff >> step);
Packit ae235b
	  --step;
Packit ae235b
	  do
Packit ae235b
	    {
Packit ae235b
	      buf[step] = 0x80 | (val & 0x3f);
Packit ae235b
	      val >>= 6;
Packit ae235b
	    }
Packit ae235b
	  while (--step > 0);
Packit ae235b
	  *buf |= val;
Packit ae235b
	}
Packit ae235b
    }
Packit ae235b
Packit ae235b
  return retval;
Packit ae235b
}
Packit ae235b
#endif /* __STDC_ISO_10646__ */
Packit ae235b
Packit ae235b
#ifdef HAVE_CARBON
Packit ae235b
Packit ae235b
static gchar *
Packit ae235b
collate_key_to_string (UCCollationValue *key,
Packit ae235b
                       gsize             key_len)
Packit ae235b
{
Packit ae235b
  gchar *result;
Packit ae235b
  gsize result_len;
Packit ae235b
  long *lkey = (long *) key;
Packit ae235b
Packit ae235b
  /* UCCollationValue format:
Packit ae235b
   *
Packit ae235b
   * UCCollateOptions (32/64 bits)
Packit ae235b
   * SizeInBytes      (32/64 bits)
Packit ae235b
   * Value            (8 bits arrey)
Packit ae235b
   *
Packit ae235b
   * UCCollateOptions: ordering option mask of the collator
Packit ae235b
   * used to create the key. Size changes on 32-bit / 64-bit
Packit ae235b
   * hosts. On 64-bits also the extra half-word seems to have
Packit ae235b
   * some extra (unknown) meaning.
Packit ae235b
   * SizeInBytes: size of the whole structure, in bytes
Packit ae235b
   * (including UCCollateOptions and SizeInBytes fields). Size
Packit ae235b
   * changes on 32-bit & 64-bit hosts.
Packit ae235b
   * Value: array of bytes containing the comparison weights.
Packit ae235b
   * Seems to have several sub-strings separated by \001 and \002
Packit ae235b
   * chars. Also, experience shows this is directly strcmp-able.
Packit ae235b
   */
Packit ae235b
Packit ae235b
  result_len = lkey[1];
Packit ae235b
  result = g_malloc (result_len + 1);
Packit ae235b
  memcpy (result, &lkey[2], result_len);
Packit ae235b
  result[result_len] = '\0';
Packit ae235b
Packit ae235b
  return result;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gchar *
Packit ae235b
carbon_collate_key_with_collator (const gchar *str,
Packit ae235b
                                  gssize       len,
Packit ae235b
                                  CollatorRef  collator)
Packit ae235b
{
Packit ae235b
  UniChar *str_utf16 = NULL;
Packit ae235b
  glong len_utf16;
Packit ae235b
  OSStatus ret;
Packit ae235b
  UCCollationValue staticbuf[512];
Packit ae235b
  UCCollationValue *freeme = NULL;
Packit ae235b
  UCCollationValue *buf;
Packit ae235b
  ItemCount buf_len;
Packit ae235b
  ItemCount key_len;
Packit ae235b
  ItemCount try_len;
Packit ae235b
  gchar *result = NULL;
Packit ae235b
Packit ae235b
  str_utf16 = g_utf8_to_utf16 (str, len, NULL, &len_utf16, NULL);
Packit ae235b
  try_len = len_utf16 * 5 + 2;
Packit ae235b
Packit ae235b
  if (try_len <= sizeof staticbuf)
Packit ae235b
    {
Packit ae235b
      buf = staticbuf;
Packit ae235b
      buf_len = sizeof staticbuf;
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    {
Packit ae235b
      freeme = g_new (UCCollationValue, try_len);
Packit ae235b
      buf = freeme;
Packit ae235b
      buf_len = try_len;
Packit ae235b
    }
Packit ae235b
Packit ae235b
  ret = UCGetCollationKey (collator, str_utf16, len_utf16,
Packit ae235b
                           buf_len, &key_len, buf);
Packit ae235b
Packit ae235b
  if (ret == kCollateBufferTooSmall)
Packit ae235b
    {
Packit ae235b
      freeme = g_renew (UCCollationValue, freeme, try_len * 2);
Packit ae235b
      buf = freeme;
Packit ae235b
      buf_len = try_len * 2;
Packit ae235b
      ret = UCGetCollationKey (collator, str_utf16, len_utf16,
Packit ae235b
                               buf_len, &key_len, buf);
Packit ae235b
    }
Packit ae235b
Packit ae235b
  if (ret == 0)
Packit ae235b
    result = collate_key_to_string (buf, key_len);
Packit ae235b
  else
Packit ae235b
    result = g_strdup ("");
Packit ae235b
Packit ae235b
  g_free (freeme);
Packit ae235b
  g_free (str_utf16);
Packit ae235b
  return result;
Packit ae235b
}
Packit ae235b
Packit ae235b
static gchar *
Packit ae235b
carbon_collate_key (const gchar *str,
Packit ae235b
                    gssize       len)
Packit ae235b
{
Packit ae235b
  static CollatorRef collator;
Packit ae235b
Packit ae235b
  if (G_UNLIKELY (!collator))
Packit ae235b
    {
Packit ae235b
      UCCreateCollator (NULL, 0, kUCCollateStandardOptions, &collator);
Packit ae235b
Packit ae235b
      if (!collator)
Packit ae235b
        {
Packit ae235b
          static gboolean been_here;
Packit ae235b
          if (!been_here)
Packit ae235b
            g_warning ("%s: UCCreateCollator failed", G_STRLOC);
Packit ae235b
          been_here = TRUE;
Packit ae235b
          return g_strdup ("");
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
Packit ae235b
  return carbon_collate_key_with_collator (str, len, collator);
Packit ae235b
}
Packit ae235b
Packit ae235b
static gchar *
Packit ae235b
carbon_collate_key_for_filename (const gchar *str,
Packit ae235b
                                 gssize       len)
Packit ae235b
{
Packit ae235b
  static CollatorRef collator;
Packit ae235b
Packit ae235b
  if (G_UNLIKELY (!collator))
Packit ae235b
    {
Packit ae235b
      /* http://developer.apple.com/qa/qa2004/qa1159.html */
Packit ae235b
      UCCreateCollator (NULL, 0,
Packit ae235b
                        kUCCollateComposeInsensitiveMask
Packit ae235b
                         | kUCCollateWidthInsensitiveMask
Packit ae235b
                         | kUCCollateCaseInsensitiveMask
Packit ae235b
                         | kUCCollateDigitsOverrideMask
Packit ae235b
                         | kUCCollateDigitsAsNumberMask
Packit ae235b
                         | kUCCollatePunctuationSignificantMask, 
Packit ae235b
                        &collator);
Packit ae235b
Packit ae235b
      if (!collator)
Packit ae235b
        {
Packit ae235b
          static gboolean been_here;
Packit ae235b
          if (!been_here)
Packit ae235b
            g_warning ("%s: UCCreateCollator failed", G_STRLOC);
Packit ae235b
          been_here = TRUE;
Packit ae235b
          return g_strdup ("");
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
Packit ae235b
  return carbon_collate_key_with_collator (str, len, collator);
Packit ae235b
}
Packit ae235b
Packit ae235b
#endif /* HAVE_CARBON */
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_utf8_collate_key:
Packit ae235b
 * @str: a UTF-8 encoded string.
Packit ae235b
 * @len: length of @str, in bytes, or -1 if @str is nul-terminated.
Packit ae235b
 *
Packit ae235b
 * Converts a string into a collation key that can be compared
Packit ae235b
 * with other collation keys produced by the same function using 
Packit ae235b
 * strcmp(). 
Packit ae235b
 *
Packit ae235b
 * The results of comparing the collation keys of two strings 
Packit ae235b
 * with strcmp() will always be the same as comparing the two 
Packit ae235b
 * original keys with g_utf8_collate().
Packit ae235b
 * 
Packit ae235b
 * Note that this function depends on the [current locale][setlocale].
Packit ae235b
 * 
Packit ae235b
 * Returns: a newly allocated string. This string should
Packit ae235b
 *   be freed with g_free() when you are done with it.
Packit ae235b
 **/
Packit ae235b
gchar *
Packit ae235b
g_utf8_collate_key (const gchar *str,
Packit ae235b
		    gssize       len)
Packit ae235b
{
Packit ae235b
  gchar *result;
Packit ae235b
Packit ae235b
#ifdef HAVE_CARBON
Packit ae235b
Packit ae235b
  g_return_val_if_fail (str != NULL, NULL);
Packit ae235b
  result = carbon_collate_key (str, len);
Packit ae235b
Packit ae235b
#elif defined(__STDC_ISO_10646__)
Packit ae235b
Packit ae235b
  gsize xfrm_len;
Packit ae235b
  gunichar *str_norm;
Packit ae235b
  wchar_t *result_wc;
Packit ae235b
  gsize i;
Packit ae235b
  gsize result_len = 0;
Packit ae235b
Packit ae235b
  g_return_val_if_fail (str != NULL, NULL);
Packit ae235b
Packit ae235b
  str_norm = _g_utf8_normalize_wc (str, len, G_NORMALIZE_ALL_COMPOSE);
Packit ae235b
Packit ae235b
  xfrm_len = wcsxfrm (NULL, (wchar_t *)str_norm, 0);
Packit ae235b
  result_wc = g_new (wchar_t, xfrm_len + 1);
Packit ae235b
  wcsxfrm (result_wc, (wchar_t *)str_norm, xfrm_len + 1);
Packit ae235b
Packit ae235b
  for (i = 0; i < xfrm_len; i++)
Packit ae235b
    result_len += utf8_encode (NULL, result_wc[i]);
Packit ae235b
Packit ae235b
  result = g_malloc (result_len + 1);
Packit ae235b
  result_len = 0;
Packit ae235b
  for (i = 0; i < xfrm_len; i++)
Packit ae235b
    result_len += utf8_encode (result + result_len, result_wc[i]);
Packit ae235b
Packit ae235b
  result[result_len] = '\0';
Packit ae235b
Packit ae235b
  g_free (result_wc);
Packit ae235b
  g_free (str_norm);
Packit ae235b
Packit ae235b
  return result;
Packit ae235b
#else /* !__STDC_ISO_10646__ */
Packit ae235b
Packit ae235b
  gsize xfrm_len;
Packit ae235b
  const gchar *charset;
Packit ae235b
  gchar *str_norm;
Packit ae235b
Packit ae235b
  g_return_val_if_fail (str != NULL, NULL);
Packit ae235b
Packit ae235b
  str_norm = g_utf8_normalize (str, len, G_NORMALIZE_ALL_COMPOSE);
Packit ae235b
Packit ae235b
  result = NULL;
Packit ae235b
Packit ae235b
  if (g_get_charset (&charset))
Packit ae235b
    {
Packit ae235b
      xfrm_len = strxfrm (NULL, str_norm, 0);
Packit ae235b
      if (xfrm_len >= 0 && xfrm_len < G_MAXINT - 2)
Packit ae235b
        {
Packit ae235b
          result = g_malloc (xfrm_len + 1);
Packit ae235b
          strxfrm (result, str_norm, xfrm_len + 1);
Packit ae235b
        }
Packit ae235b
    }
Packit ae235b
  else
Packit ae235b
    {
Packit ae235b
      gchar *str_locale = g_convert (str_norm, -1, charset, "UTF-8", NULL, NULL, NULL);
Packit ae235b
Packit ae235b
      if (str_locale)
Packit ae235b
	{
Packit ae235b
	  xfrm_len = strxfrm (NULL, str_locale, 0);
Packit ae235b
	  if (xfrm_len < 0 || xfrm_len >= G_MAXINT - 2)
Packit ae235b
	    {
Packit ae235b
	      g_free (str_locale);
Packit ae235b
	      str_locale = NULL;
Packit ae235b
	    }
Packit ae235b
	}
Packit ae235b
      if (str_locale)
Packit ae235b
	{
Packit ae235b
	  result = g_malloc (xfrm_len + 2);
Packit ae235b
	  result[0] = 'A';
Packit ae235b
	  strxfrm (result + 1, str_locale, xfrm_len + 1);
Packit ae235b
	  
Packit ae235b
	  g_free (str_locale);
Packit ae235b
	}
Packit ae235b
    }
Packit ae235b
    
Packit ae235b
  if (!result) 
Packit ae235b
    {
Packit ae235b
      xfrm_len = strlen (str_norm);
Packit ae235b
      result = g_malloc (xfrm_len + 2);
Packit ae235b
      result[0] = 'B';
Packit ae235b
      memcpy (result + 1, str_norm, xfrm_len);
Packit ae235b
      result[xfrm_len+1] = '\0';
Packit ae235b
    }
Packit ae235b
Packit ae235b
  g_free (str_norm);
Packit ae235b
#endif /* __STDC_ISO_10646__ */
Packit ae235b
Packit ae235b
  return result;
Packit ae235b
}
Packit ae235b
Packit ae235b
/* This is a collation key that is very very likely to sort before any
Packit ae235b
 * collation key that libc strxfrm generates. We use this before any
Packit ae235b
 * special case (dot or number) to make sure that its sorted before
Packit ae235b
 * anything else.
Packit ae235b
 */
Packit ae235b
#define COLLATION_SENTINEL "\1\1\1"
Packit ae235b
Packit ae235b
/**
Packit ae235b
 * g_utf8_collate_key_for_filename:
Packit ae235b
 * @str: a UTF-8 encoded string.
Packit ae235b
 * @len: length of @str, in bytes, or -1 if @str is nul-terminated.
Packit ae235b
 *
Packit ae235b
 * Converts a string into a collation key that can be compared
Packit ae235b
 * with other collation keys produced by the same function using strcmp(). 
Packit ae235b
 * 
Packit ae235b
 * In order to sort filenames correctly, this function treats the dot '.' 
Packit ae235b
 * as a special case. Most dictionary orderings seem to consider it
Packit ae235b
 * insignificant, thus producing the ordering "event.c" "eventgenerator.c"
Packit ae235b
 * "event.h" instead of "event.c" "event.h" "eventgenerator.c". Also, we
Packit ae235b
 * would like to treat numbers intelligently so that "file1" "file10" "file5"
Packit ae235b
 * is sorted as "file1" "file5" "file10".
Packit ae235b
 * 
Packit ae235b
 * Note that this function depends on the [current locale][setlocale].
Packit ae235b
 *
Packit ae235b
 * Returns: a newly allocated string. This string should
Packit ae235b
 *   be freed with g_free() when you are done with it.
Packit ae235b
 *
Packit ae235b
 * Since: 2.8
Packit ae235b
 */
Packit ae235b
gchar *
Packit ae235b
g_utf8_collate_key_for_filename (const gchar *str,
Packit ae235b
				 gssize       len)
Packit ae235b
{
Packit ae235b
#ifndef HAVE_CARBON
Packit ae235b
  GString *result;
Packit ae235b
  GString *append;
Packit ae235b
  const gchar *p;
Packit ae235b
  const gchar *prev;
Packit ae235b
  const gchar *end;
Packit ae235b
  gchar *collate_key;
Packit ae235b
  gint digits;
Packit ae235b
  gint leading_zeros;
Packit ae235b
Packit ae235b
  /*
Packit ae235b
   * How it works:
Packit ae235b
   *
Packit ae235b
   * Split the filename into collatable substrings which do
Packit ae235b
   * not contain [.0-9] and special-cased substrings. The collatable 
Packit ae235b
   * substrings are run through the normal g_utf8_collate_key() and the 
Packit ae235b
   * resulting keys are concatenated with keys generated from the 
Packit ae235b
   * special-cased substrings.
Packit ae235b
   *
Packit ae235b
   * Special cases: Dots are handled by replacing them with '\1' which 
Packit ae235b
   * implies that short dot-delimited substrings are before long ones, 
Packit ae235b
   * e.g.
Packit ae235b
   * 
Packit ae235b
   *   a\1a   (a.a)
Packit ae235b
   *   a-\1a  (a-.a)
Packit ae235b
   *   aa\1a  (aa.a)
Packit ae235b
   * 
Packit ae235b
   * Numbers are handled by prepending to each number d-1 superdigits 
Packit ae235b
   * where d = number of digits in the number and SUPERDIGIT is a 
Packit ae235b
   * character with an integer value higher than any digit (for instance 
Packit ae235b
   * ':'). This ensures that single-digit numbers are sorted before 
Packit ae235b
   * double-digit numbers which in turn are sorted separately from 
Packit ae235b
   * triple-digit numbers, etc. To avoid strange side-effects when 
Packit ae235b
   * sorting strings that already contain SUPERDIGITs, a '\2'
Packit ae235b
   * is also prepended, like this
Packit ae235b
   *
Packit ae235b
   *   file\21      (file1)
Packit ae235b
   *   file\25      (file5)
Packit ae235b
   *   file\2:10    (file10)
Packit ae235b
   *   file\2:26    (file26)
Packit ae235b
   *   file\2::100  (file100)
Packit ae235b
   *   file:foo     (file:foo)
Packit ae235b
   * 
Packit ae235b
   * This has the side-effect of sorting numbers before everything else (except
Packit ae235b
   * dots), but this is probably OK.
Packit ae235b
   *
Packit ae235b
   * Leading digits are ignored when doing the above. To discriminate
Packit ae235b
   * numbers which differ only in the number of leading digits, we append
Packit ae235b
   * the number of leading digits as a byte at the very end of the collation
Packit ae235b
   * key.
Packit ae235b
   *
Packit ae235b
   * To try avoid conflict with any collation key sequence generated by libc we
Packit ae235b
   * start each switch to a special cased part with a sentinel that hopefully
Packit ae235b
   * will sort before anything libc will generate.
Packit ae235b
   */
Packit ae235b
Packit ae235b
  if (len < 0)
Packit ae235b
    len = strlen (str);
Packit ae235b
Packit ae235b
  result = g_string_sized_new (len * 2);
Packit ae235b
  append = g_string_sized_new (0);
Packit ae235b
Packit ae235b
  end = str + len;
Packit ae235b
Packit ae235b
  /* No need to use utf8 functions, since we're only looking for ascii chars */
Packit ae235b
  for (prev = p = str; p < end; p++)
Packit ae235b
    {
Packit ae235b
      switch (*p)
Packit ae235b
	{
Packit ae235b
	case '.':
Packit ae235b
	  if (prev != p) 
Packit ae235b
	    {
Packit ae235b
	      collate_key = g_utf8_collate_key (prev, p - prev);
Packit ae235b
	      g_string_append (result, collate_key);
Packit ae235b
	      g_free (collate_key);
Packit ae235b
	    }
Packit ae235b
	  
Packit ae235b
	  g_string_append (result, COLLATION_SENTINEL "\1");
Packit ae235b
	  
Packit ae235b
	  /* skip the dot */
Packit ae235b
	  prev = p + 1;
Packit ae235b
	  break;
Packit ae235b
	  
Packit ae235b
	case '0':
Packit ae235b
	case '1':
Packit ae235b
	case '2':
Packit ae235b
	case '3':
Packit ae235b
	case '4':
Packit ae235b
	case '5':
Packit ae235b
	case '6':
Packit ae235b
	case '7':
Packit ae235b
	case '8':
Packit ae235b
	case '9':
Packit ae235b
	  if (prev != p) 
Packit ae235b
	    {
Packit ae235b
	      collate_key = g_utf8_collate_key (prev, p - prev);
Packit ae235b
	      g_string_append (result, collate_key);
Packit ae235b
	      g_free (collate_key);
Packit ae235b
	    }
Packit ae235b
	  
Packit ae235b
	  g_string_append (result, COLLATION_SENTINEL "\2");
Packit ae235b
	  
Packit ae235b
	  prev = p;
Packit ae235b
	  
Packit ae235b
	  /* write d-1 colons */
Packit ae235b
	  if (*p == '0')
Packit ae235b
	    {
Packit ae235b
	      leading_zeros = 1;
Packit ae235b
	      digits = 0;
Packit ae235b
	    }
Packit ae235b
	  else
Packit ae235b
	    {
Packit ae235b
	      leading_zeros = 0;
Packit ae235b
	      digits = 1;
Packit ae235b
	    }
Packit ae235b
	  
Packit ae235b
	  while (++p < end)
Packit ae235b
	    {
Packit ae235b
	      if (*p == '0' && !digits)
Packit ae235b
		++leading_zeros;
Packit ae235b
	      else if (g_ascii_isdigit(*p))
Packit ae235b
		++digits;
Packit ae235b
	      else
Packit ae235b
                {
Packit ae235b
 		  /* count an all-zero sequence as
Packit ae235b
                   * one digit plus leading zeros
Packit ae235b
                   */
Packit ae235b
          	  if (!digits)
Packit ae235b
                    {
Packit ae235b
                      ++digits;
Packit ae235b
                      --leading_zeros;
Packit ae235b
                    }        
Packit ae235b
		  break;
Packit ae235b
                }
Packit ae235b
	    }
Packit ae235b
Packit ae235b
	  while (digits > 1)
Packit ae235b
	    {
Packit ae235b
	      g_string_append_c (result, ':');
Packit ae235b
	      --digits;
Packit ae235b
	    }
Packit ae235b
Packit ae235b
	  if (leading_zeros > 0)
Packit ae235b
	    {
Packit ae235b
	      g_string_append_c (append, (char)leading_zeros);
Packit ae235b
	      prev += leading_zeros;
Packit ae235b
	    }
Packit ae235b
	  
Packit ae235b
	  /* write the number itself */
Packit ae235b
	  g_string_append_len (result, prev, p - prev);
Packit ae235b
	  
Packit ae235b
	  prev = p;
Packit ae235b
	  --p;	  /* go one step back to avoid disturbing outer loop */
Packit ae235b
	  break;
Packit ae235b
	  
Packit ae235b
	default:
Packit ae235b
	  /* other characters just accumulate */
Packit ae235b
	  break;
Packit ae235b
	}
Packit ae235b
    }
Packit ae235b
  
Packit ae235b
  if (prev != p) 
Packit ae235b
    {
Packit ae235b
      collate_key = g_utf8_collate_key (prev, p - prev);
Packit ae235b
      g_string_append (result, collate_key);
Packit ae235b
      g_free (collate_key);
Packit ae235b
    }
Packit ae235b
  
Packit ae235b
  g_string_append (result, append->str);
Packit ae235b
  g_string_free (append, TRUE);
Packit ae235b
Packit ae235b
  return g_string_free (result, FALSE);
Packit ae235b
#else /* HAVE_CARBON */
Packit ae235b
  return carbon_collate_key_for_filename (str, len);
Packit ae235b
#endif
Packit ae235b
}