LCOV - code coverage report
Current view: top level - glib - gtranslit.c (source / functions) Coverage Total Hit
Test: unnamed Lines: 100.0 % 120 120
Test Date: 2024-10-08 05:20:27 Functions: 100.0 % 9 9
Branches: - 0 0

             Branch data     Line data    Source code
       1                 :             : /*
       2                 :             :  * Copyright © 2014 Canonical Limited
       3                 :             :  *
       4                 :             :  * SPDX-License-Identifier: LGPL-2.1-or-later
       5                 :             :  *
       6                 :             :  * This library is free software; you can redistribute it and/or
       7                 :             :  * modify it under the terms of the GNU Lesser General Public
       8                 :             :  * License as published by the Free Software Foundation; either
       9                 :             :  * version 2.1 of the License, or (at your option) any later version.
      10                 :             :  *
      11                 :             :  * This library is distributed in the hope that it will be useful,
      12                 :             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      13                 :             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      14                 :             :  * Lesser General Public License for more details.
      15                 :             :  *
      16                 :             :  * You should have received a copy of the GNU Lesser General Public
      17                 :             :  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
      18                 :             :  *
      19                 :             :  * Author: Ryan Lortie <desrt@desrt.ca>
      20                 :             :  */
      21                 :             : 
      22                 :             : #include <config.h>
      23                 :             : 
      24                 :             : #include "gstrfuncs.h"
      25                 :             : 
      26                 :             : #include <glib.h>
      27                 :             : #include <locale.h>
      28                 :             : #include <stdlib.h>
      29                 :             : #include <string.h>
      30                 :             : 
      31                 :             : struct mapping_entry
      32                 :             : {
      33                 :             :   guint16 src;
      34                 :             :   guint16 ascii;
      35                 :             : };
      36                 :             : 
      37                 :             : struct mapping_range
      38                 :             : {
      39                 :             :   guint16 start;
      40                 :             :   guint16 length;
      41                 :             : };
      42                 :             : 
      43                 :             : struct locale_entry
      44                 :             : {
      45                 :             :   guint8 name_offset;
      46                 :             :   guint8 item_id;
      47                 :             : };
      48                 :             : 
      49                 :             : #include "gtranslit-data.h"
      50                 :             : 
      51                 :             : #define get_src_char(array, encoded, index) ((encoded & 0x8000) ? (array)[((encoded) & 0xfff) + index] : encoded)
      52                 :             : #define get_length(encoded)                 ((encoded & 0x8000) ? ((encoded & 0x7000) >> 12) : 1)
      53                 :             : 
      54                 :             : #if G_BYTE_ORDER == G_BIG_ENDIAN
      55                 :             : #define get_ascii_item(array, encoded)      ((encoded & 0x8000) ? &(array)[(encoded) & 0xfff] : (gpointer) (((char *) &(encoded)) + 1))
      56                 :             : #else
      57                 :             : #define get_ascii_item(array, encoded)      ((encoded & 0x8000) ? &(array)[(encoded) & 0xfff] : (gpointer) &(encoded))
      58                 :             : #endif
      59                 :             : 
      60                 :             : static const gchar * lookup_in_item (guint           item_id,
      61                 :             :                                      const gunichar *key,
      62                 :             :                                      gint           *result_len,
      63                 :             :                                      gint           *key_consumed);
      64                 :             : 
      65                 :             : static gint
      66                 :         891 : compare_mapping_entry (gconstpointer user_data,
      67                 :             :                        gconstpointer data)
      68                 :             : {
      69                 :         891 :   const struct mapping_entry *entry = data;
      70                 :         891 :   const gunichar *key = user_data;
      71                 :             :   gunichar src_0;
      72                 :             : 
      73                 :             :   G_STATIC_ASSERT(MAX_KEY_SIZE == 2);
      74                 :             : 
      75                 :         891 :   src_0 = get_src_char (src_table, entry->src, 0);
      76                 :             : 
      77                 :         891 :   if (key[0] > src_0)
      78                 :         306 :     return 1;
      79                 :         585 :   else if (key[0] < src_0)
      80                 :         493 :     return -1;
      81                 :             : 
      82                 :          92 :   if (get_length (entry->src) > 1)
      83                 :           4 :     {
      84                 :             :       gunichar src_1;
      85                 :             : 
      86                 :          12 :       src_1 = get_src_char (src_table, entry->src, 1);
      87                 :             : 
      88                 :          12 :       if (key[1] > src_1)
      89                 :           4 :         return 1;
      90                 :           8 :       else if (key[1] < src_1)
      91                 :           4 :         return -1;
      92                 :             :     }
      93                 :          80 :   else if (key[1])
      94                 :          20 :     return 1;
      95                 :             : 
      96                 :          64 :   return 0;
      97                 :             : }
      98                 :             : 
      99                 :             : static const gchar *
     100                 :         115 : lookup_in_mapping (const struct mapping_entry *mapping,
     101                 :             :                    gint                        mapping_size,
     102                 :             :                    const gunichar             *key,
     103                 :             :                    gint                       *result_len,
     104                 :             :                    gint                       *key_consumed)
     105                 :             : {
     106                 :             :   const struct mapping_entry *hit;
     107                 :             : 
     108                 :         115 :   hit = bsearch (key, mapping, mapping_size, sizeof (struct mapping_entry), compare_mapping_entry);
     109                 :             : 
     110                 :         115 :   if (hit == NULL)
     111                 :          51 :     return NULL;
     112                 :             : 
     113                 :          64 :   *key_consumed = get_length (hit->src);
     114                 :          64 :   *result_len = get_length (hit->ascii);
     115                 :             : 
     116                 :          64 :   return get_ascii_item(ascii_table, hit->ascii);
     117                 :             : }
     118                 :             : 
     119                 :             : static const gchar *
     120                 :          87 : lookup_in_chain (const guint8   *chain,
     121                 :             :                  const gunichar *key,
     122                 :             :                  gint           *result_len,
     123                 :             :                  gint           *key_consumed)
     124                 :             : {
     125                 :             :   const gchar *result;
     126                 :             : 
     127                 :         137 :   while (*chain != 0xff)
     128                 :             :     {
     129                 :         114 :       result = lookup_in_item (*chain, key, result_len, key_consumed);
     130                 :             : 
     131                 :         114 :       if (result)
     132                 :          64 :         return result;
     133                 :             : 
     134                 :          50 :       chain++;
     135                 :             :     }
     136                 :             : 
     137                 :          23 :   return NULL;
     138                 :             : }
     139                 :             : 
     140                 :             : static const gchar *
     141                 :         202 : lookup_in_item (guint           item_id,
     142                 :             :                 const gunichar *key,
     143                 :             :                 gint           *result_len,
     144                 :             :                 gint           *key_consumed)
     145                 :             : {
     146                 :         202 :   if (item_id & 0x80)
     147                 :             :     {
     148                 :          87 :       const guint8 *chain = chains_table + chain_starts[item_id & 0x7f];
     149                 :             : 
     150                 :          87 :       return lookup_in_chain (chain, key, result_len, key_consumed);
     151                 :             :     }
     152                 :             :   else
     153                 :             :     {
     154                 :         115 :       const struct mapping_range *range = &mapping_ranges[item_id];
     155                 :             : 
     156                 :         115 :       return lookup_in_mapping (mappings_table + range->start, range->length, key, result_len, key_consumed);
     157                 :             :     }
     158                 :             : }
     159                 :             : 
     160                 :             : static gint
     161                 :         158 : compare_locale_entry (gconstpointer user_data,
     162                 :             :                       gconstpointer data)
     163                 :             : {
     164                 :         158 :   const struct locale_entry *entry = data;
     165                 :         158 :   const gchar *key = user_data;
     166                 :             : 
     167                 :         158 :   return strcmp (key, &locale_names[entry->name_offset]);
     168                 :             : }
     169                 :             : 
     170                 :             : static gboolean
     171                 :          27 : lookup_item_id_for_one_locale (const gchar *key,
     172                 :             :                                guint       *item_id)
     173                 :             : {
     174                 :             :   const struct locale_entry *hit;
     175                 :             : 
     176                 :          27 :   hit = bsearch (key, locale_index, G_N_ELEMENTS (locale_index), sizeof (struct locale_entry), compare_locale_entry);
     177                 :             : 
     178                 :          27 :   if (hit == NULL)
     179                 :          15 :     return FALSE;
     180                 :             : 
     181                 :          12 :   *item_id = hit->item_id;
     182                 :          12 :   return TRUE;
     183                 :             : }
     184                 :             : 
     185                 :             : static guint
     186                 :          22 : lookup_item_id_for_locale (const gchar *locale)
     187                 :             : {
     188                 :             :   gchar key[MAX_LOCALE_NAME + 1];
     189                 :             :   const gchar *language;
     190                 :             :   size_t language_len;
     191                 :          22 :   const gchar *territory = NULL;
     192                 :          22 :   size_t territory_len = 0;
     193                 :          22 :   const gchar *modifier = NULL;
     194                 :          22 :   size_t modifier_len = 0;
     195                 :             :   const gchar *next_char;
     196                 :             :   guint id;
     197                 :             : 
     198                 :             :   /* As per POSIX, a valid locale looks like:
     199                 :             :    *
     200                 :             :    *   language[_territory][.codeset][@modifier]
     201                 :             :    */
     202                 :          22 :   language = locale;
     203                 :          22 :   language_len = strcspn (language, "_.@");
     204                 :          22 :   next_char = language + language_len;
     205                 :             : 
     206                 :          22 :   if (*next_char == '_')
     207                 :             :     {
     208                 :          10 :       territory = next_char;
     209                 :          10 :       territory_len = strcspn (territory + 1, "_.@") + 1;
     210                 :          10 :       next_char = territory + territory_len;
     211                 :             :     }
     212                 :             : 
     213                 :          22 :   if (*next_char == '.')
     214                 :             :     {
     215                 :             :       const gchar *codeset;
     216                 :             :       guint codeset_len;
     217                 :             : 
     218                 :           5 :       codeset = next_char;
     219                 :           5 :       codeset_len = strcspn (codeset + 1, "_.@") + 1;
     220                 :           5 :       next_char = codeset + codeset_len;
     221                 :             :     }
     222                 :             : 
     223                 :          22 :   if (*next_char == '@')
     224                 :             :     {
     225                 :           5 :       modifier = next_char;
     226                 :           5 :       modifier_len = strcspn (modifier + 1, "_.@") + 1;
     227                 :           5 :       next_char = modifier + modifier_len;
     228                 :             :     }
     229                 :             : 
     230                 :             :   /* What madness is this? */
     231                 :          22 :   if (language_len == 0 || *next_char)
     232                 :           2 :     return default_item_id;
     233                 :             : 
     234                 :             :   /* We are not interested in codeset.
     235                 :             :    *
     236                 :             :    * For this locale:
     237                 :             :    *
     238                 :             :    *  aa_BB@cc
     239                 :             :    *
     240                 :             :    * try in this order:
     241                 :             :    *
     242                 :             :    * Note: we have no locales of the form aa_BB@cc in the database.
     243                 :             :    *
     244                 :             :    *  1. aa@cc
     245                 :             :    *  2. aa_BB
     246                 :             :    *  3. aa
     247                 :             :    */
     248                 :             : 
     249                 :             :   /* 1. */
     250                 :          20 :   if (modifier_len && language_len + modifier_len <= MAX_LOCALE_NAME)
     251                 :             :     {
     252                 :           3 :       memcpy (key, language, language_len);
     253                 :           3 :       memcpy (key + language_len, modifier, modifier_len);
     254                 :           3 :       key[language_len + modifier_len] = '\0';
     255                 :             : 
     256                 :           3 :       if (lookup_item_id_for_one_locale (key, &id))
     257                 :           1 :         return id;
     258                 :             :     }
     259                 :             : 
     260                 :             :   /* 2. */
     261                 :          19 :   if (territory_len && language_len + territory_len <= MAX_LOCALE_NAME)
     262                 :             :     {
     263                 :           8 :       memcpy (key, language, language_len);
     264                 :           8 :       memcpy (key + language_len, territory, territory_len);
     265                 :           8 :       key[language_len + territory_len] = '\0';
     266                 :             : 
     267                 :           8 :       if (lookup_item_id_for_one_locale (key, &id))
     268                 :           1 :         return id;
     269                 :             :     }
     270                 :             : 
     271                 :             :   /* 3. */
     272                 :          18 :   if (language_len <= MAX_LOCALE_NAME)
     273                 :             :     {
     274                 :          16 :       memcpy (key, language, language_len);
     275                 :          16 :       key[language_len] = '\0';
     276                 :             : 
     277                 :          16 :       if (lookup_item_id_for_one_locale (key, &id))
     278                 :          10 :         return id;
     279                 :             :     }
     280                 :             : 
     281                 :           8 :   return default_item_id;
     282                 :             : }
     283                 :             : 
     284                 :             : static guint
     285                 :          22 : get_default_item_id (void)
     286                 :             : {
     287                 :             :   static guint item_id;
     288                 :             :   static gboolean done;
     289                 :             : 
     290                 :             :   /* Doesn't need to be locked -- no harm in doing it twice. */
     291                 :          22 :   if (!done)
     292                 :             :     {
     293                 :             :       const gchar *locale;
     294                 :             : 
     295                 :           4 :       locale = setlocale (LC_CTYPE, NULL);
     296                 :           4 :       item_id = lookup_item_id_for_locale (locale);
     297                 :           4 :       done = TRUE;
     298                 :             :     }
     299                 :             : 
     300                 :          22 :   return item_id;
     301                 :             : }
     302                 :             : 
     303                 :             : /**
     304                 :             :  * g_str_to_ascii:
     305                 :             :  * @str: a string, in UTF-8
     306                 :             :  * @from_locale: (nullable): the source locale, if known
     307                 :             :  *
     308                 :             :  * Transliterate @str to plain ASCII.
     309                 :             :  *
     310                 :             :  * For best results, @str should be in composed normalised form.
     311                 :             :  *
     312                 :             :  * This function performs a reasonably good set of character
     313                 :             :  * replacements.  The particular set of replacements that is done may
     314                 :             :  * change by version or even by runtime environment.
     315                 :             :  *
     316                 :             :  * If the source language of @str is known, it can used to improve the
     317                 :             :  * accuracy of the translation by passing it as @from_locale.  It should
     318                 :             :  * be a valid POSIX locale string (of the form
     319                 :             :  * `language[_territory][.codeset][@modifier]`).
     320                 :             :  *
     321                 :             :  * If @from_locale is %NULL then the current locale is used.
     322                 :             :  *
     323                 :             :  * If you want to do translation for no specific locale, and you want it
     324                 :             :  * to be done independently of the currently locale, specify `"C"` for
     325                 :             :  * @from_locale.
     326                 :             :  *
     327                 :             :  * Returns: a string in plain ASCII
     328                 :             :  *
     329                 :             :  * Since: 2.40
     330                 :             :  **/
     331                 :             : gchar *
     332                 :          41 : g_str_to_ascii (const gchar *str,
     333                 :             :                 const gchar *from_locale)
     334                 :             : {
     335                 :             :   GString *result;
     336                 :             :   guint item_id;
     337                 :             : 
     338                 :          41 :   g_return_val_if_fail (str != NULL, NULL);
     339                 :             : 
     340                 :          41 :   if (g_str_is_ascii (str))
     341                 :           1 :     return g_strdup (str);
     342                 :             : 
     343                 :          40 :   if (from_locale)
     344                 :          18 :     item_id = lookup_item_id_for_locale (from_locale);
     345                 :             :   else
     346                 :          22 :     item_id = get_default_item_id ();
     347                 :             : 
     348                 :          40 :   result = g_string_sized_new (strlen (str));
     349                 :             : 
     350                 :         233 :   while (*str)
     351                 :             :     {
     352                 :             :       /* We only need to transliterate non-ASCII values... */
     353                 :         193 :       if (*str & 0x80)
     354                 :             :         {
     355                 :             :           gunichar key[MAX_KEY_SIZE];
     356                 :             :           const gchar *r;
     357                 :             :           gint consumed;
     358                 :             :           gint r_len;
     359                 :             :           gunichar c;
     360                 :             : 
     361                 :             :           G_STATIC_ASSERT(MAX_KEY_SIZE == 2);
     362                 :             : 
     363                 :          67 :           c = g_utf8_get_char (str);
     364                 :             : 
     365                 :             :           /* This is where it gets evil...
     366                 :             :            *
     367                 :             :            * We know that MAX_KEY_SIZE is 2.  We also know that we
     368                 :             :            * only want to try another character if it's non-ascii.
     369                 :             :            */
     370                 :          67 :           str = g_utf8_next_char (str);
     371                 :             : 
     372                 :          67 :           key[0] = c;
     373                 :          67 :           if (*str & 0x80)
     374                 :          25 :             key[1] = g_utf8_get_char (str);
     375                 :             :           else
     376                 :          42 :             key[1] = 0;
     377                 :             : 
     378                 :          67 :           r = lookup_in_item (item_id, key, &r_len, &consumed);
     379                 :             : 
     380                 :             :           /* If we failed to map two characters, try again with one.
     381                 :             :            *
     382                 :             :            * gconv behaviour is a bit weird here -- it seems to
     383                 :             :            * depend in the randomness of the binary search and the
     384                 :             :            * size of the input buffer as to what result we get here.
     385                 :             :            *
     386                 :             :            * Doing it this way is more work, but should be
     387                 :             :            * more-correct.
     388                 :             :            */
     389                 :          67 :           if (r == NULL && key[1])
     390                 :             :             {
     391                 :          21 :               key[1] = 0;
     392                 :          21 :               r = lookup_in_item (item_id, key, &r_len, &consumed);
     393                 :             :             }
     394                 :             : 
     395                 :          67 :           if (r != NULL)
     396                 :             :             {
     397                 :          64 :               g_string_append_len (result, r, r_len);
     398                 :          64 :               if (consumed == 2)
     399                 :             :                 /* If it took both then skip again */
     400                 :           4 :                 str = g_utf8_next_char (str);
     401                 :             :             }
     402                 :             :           else /* no match found */
     403                 :             :             g_string_append_c (result, '?');
     404                 :             :         }
     405                 :             :       else /* ASCII case */
     406                 :         126 :         g_string_append_c (result, *str++);
     407                 :             :     }
     408                 :             : 
     409                 :          40 :   return g_string_free (result, FALSE);
     410                 :             : }
        

Generated by: LCOV version 2.0-1