Avoid question marks in proper_name_utf8 result.
[gnulib.git] / lib / propername.c
1 /* Localization of proper names.
2    Copyright (C) 2006-2008 Free Software Foundation, Inc.
3    Written by Bruno Haible <bruno@clisp.org>, 2006.
4
5    This program is free software: you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 3 of the License, or
8    (at your option) any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14
15    You should have received a copy of the GNU General Public License
16    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
17
18 #include <config.h>
19
20 /* Specification.  */
21 #include "propername.h"
22
23 #include <ctype.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #if HAVE_ICONV
29 # include <iconv.h>
30 #endif
31
32 #include "trim.h"
33 #include "mbchar.h"
34 #if HAVE_MBRTOWC
35 # include "mbuiter.h"
36 #endif
37 #include "localcharset.h"
38 #include "c-strcase.h"
39 #include "xstriconv.h"
40 #include "xalloc.h"
41 #include "gettext.h"
42
43
44 /* Tests whether STRING contains trim (SUB), starting and ending at word
45    boundaries.
46    Here, instead of implementing Unicode Standard Annex #29 for determining
47    word boundaries, we assume that trim (SUB) starts and ends with words and
48    only test whether the part before it ends with a non-word and the part
49    after it starts with a non-word.  */
50 static bool
51 mbsstr_trimmed_wordbounded (const char *string, const char *sub)
52 {
53   char *tsub = trim (sub);
54   bool found = false;
55
56   for (; *string != '\0';)
57     {
58       const char *tsub_in_string = mbsstr (string, tsub);
59       if (tsub_in_string == NULL)
60         break;
61       else
62         {
63 #if HAVE_MBRTOWC
64           if (MB_CUR_MAX > 1)
65             {
66               mbui_iterator_t string_iter;
67               bool word_boundary_before;
68               bool word_boundary_after;
69
70               mbui_init (string_iter, string);
71               word_boundary_before = true;
72               if (mbui_cur_ptr (string_iter) < tsub_in_string)
73                 {
74                   mbchar_t last_char_before_tsub;
75                   do
76                     {
77                       if (!mbui_avail (string_iter))
78                         abort ();
79                       last_char_before_tsub = mbui_cur (string_iter);
80                       mbui_advance (string_iter);
81                     }
82                   while (mbui_cur_ptr (string_iter) < tsub_in_string);
83                   if (mb_isalnum (last_char_before_tsub))
84                     word_boundary_before = false;
85                 }
86
87               mbui_init (string_iter, tsub_in_string);
88               {
89                 mbui_iterator_t tsub_iter;
90
91                 for (mbui_init (tsub_iter, tsub);
92                      mbui_avail (tsub_iter);
93                      mbui_advance (tsub_iter))
94                   {
95                     if (!mbui_avail (string_iter))
96                       abort ();
97                     mbui_advance (string_iter);
98                   }
99               }
100               word_boundary_after = true;
101               if (mbui_avail (string_iter))
102                 {
103                   mbchar_t first_char_after_tsub = mbui_cur (string_iter);
104                   if (mb_isalnum (first_char_after_tsub))
105                     word_boundary_after = false;
106                 }
107
108               if (word_boundary_before && word_boundary_after)
109                 {
110                   found = true;
111                   break;
112                 }
113
114               mbui_init (string_iter, tsub_in_string);
115               if (!mbui_avail (string_iter))
116                 break;
117               string = tsub_in_string + mb_len (mbui_cur (string_iter));
118             }
119           else
120 #endif /* HAVE_MBRTOWC */
121             {
122               bool word_boundary_before;
123               const char *p;
124               bool word_boundary_after;
125
126               word_boundary_before = true;
127               if (string < tsub_in_string)
128                 if (isalnum ((unsigned char) tsub_in_string[-1]))
129                   word_boundary_before = false;
130
131               p = tsub_in_string + strlen (tsub);
132               word_boundary_after = true;
133               if (*p != '\0')
134                 if (isalnum ((unsigned char) *p))
135                   word_boundary_after = false;
136
137               if (word_boundary_before && word_boundary_after)
138                 {
139                   found = true;
140                   break;
141                 }
142
143               if (*tsub_in_string == '\0')
144                 break;
145               string = tsub_in_string + 1;
146             }
147         }
148     }
149   free (tsub);
150   return found;
151 }
152
153 /* Return the localization of NAME.  NAME is written in ASCII.  */
154
155 const char *
156 proper_name (const char *name)
157 {
158   /* See whether there is a translation.   */
159   const char *translation = gettext (name);
160
161   if (translation != name)
162     {
163       /* See whether the translation contains the original name.  */
164       if (mbsstr_trimmed_wordbounded (translation, name))
165         return translation;
166       else
167         {
168           /* Return "TRANSLATION (NAME)".  */
169           char *result =
170             XNMALLOC (strlen (translation) + 2 + strlen (name) + 1 + 1, char);
171
172           sprintf (result, "%s (%s)", translation, name);
173           return result;
174         }
175     }
176   else
177     return name;
178 }
179
180 /* Return the localization of a name whose original writing is not ASCII.
181    NAME_UTF8 is the real name, written in UTF-8 with octal or hexadecimal
182    escape sequences.  NAME_ASCII is a fallback written only with ASCII
183    characters.  */
184
185 const char *
186 proper_name_utf8 (const char *name_ascii, const char *name_utf8)
187 {
188   /* See whether there is a translation.   */
189   const char *translation = gettext (name_ascii);
190
191   /* Try to convert NAME_UTF8 to the locale encoding.  */
192   const char *locale_code = locale_charset ();
193   char *alloc_name_converted = NULL;
194   char *alloc_name_converted_translit = NULL;
195   const char *name_converted = NULL;
196   const char *name_converted_translit = NULL;
197   const char *name;
198
199   if (c_strcasecmp (locale_code, "UTF-8") != 0)
200     {
201 #if HAVE_ICONV
202       name_converted = alloc_name_converted =
203         xstr_iconv (name_utf8, "UTF-8", locale_code);
204
205 # if (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 2) || __GLIBC__ > 2 \
206      || _LIBICONV_VERSION >= 0x0105
207       {
208         char *converted_translit;
209
210         size_t len = strlen (locale_code);
211         char *locale_code_translit = XNMALLOC (len + 10 + 1, char);
212         memcpy (locale_code_translit, locale_code, len);
213         memcpy (locale_code_translit + len, "//TRANSLIT", 10 + 1);
214
215         converted_translit =
216           xstr_iconv (name_utf8, "UTF-8", locale_code_translit);
217
218         free (locale_code_translit);
219
220         if (converted_translit != NULL)
221           {
222 #  if !_LIBICONV_VERSION
223             /* Don't use the transliteration if it added question marks.
224                glibc's transliteration falls back to question marks; libiconv's
225                transliteration does not.
226                mbschr is equivalent to strchr in this case.  */
227             if (strchr (converted_translit, '?') != NULL)
228               free (converted_translit);
229             else
230 #  endif
231               name_converted_translit = alloc_name_converted_translit =
232                 converted_translit;
233           }
234       }
235 # endif
236 #endif
237     }
238   else
239     {
240       name_converted = name_utf8;
241       name_converted_translit = name_utf8;
242     }
243
244   /* The name in locale encoding.  */
245   name = (name_converted != NULL ? name_converted :
246           name_converted_translit != NULL ? name_converted_translit :
247           name_ascii);
248
249   if (translation != name_ascii)
250     {
251       /* See whether the translation contains the original name.  */
252       if (mbsstr_trimmed_wordbounded (translation, name_ascii)
253           || (name_converted != NULL
254               && mbsstr_trimmed_wordbounded (translation, name_converted))
255           || (name_converted_translit != NULL
256               && mbsstr_trimmed_wordbounded (translation, name_converted_translit)))
257         {
258           if (alloc_name_converted != NULL)
259             free (alloc_name_converted);
260           if (alloc_name_converted_translit != NULL)
261             free (alloc_name_converted_translit);
262           return translation;
263         }
264       else
265         {
266           /* Return "TRANSLATION (NAME)".  */
267           char *result =
268             XNMALLOC (strlen (translation) + 2 + strlen (name) + 1 + 1, char);
269
270           sprintf (result, "%s (%s)", translation, name);
271
272           if (alloc_name_converted != NULL)
273             free (alloc_name_converted);
274           if (alloc_name_converted_translit != NULL)
275             free (alloc_name_converted_translit);
276           return result;
277         }
278     }
279   else
280     {
281       if (alloc_name_converted != NULL && alloc_name_converted != name)
282         free (alloc_name_converted);
283       if (alloc_name_converted_translit != NULL
284           && alloc_name_converted_translit != name)
285         free (alloc_name_converted_translit);
286       return name;
287     }
288 }
289
290 #ifdef TEST1
291 # include <locale.h>
292 int
293 main (int argc, char *argv[])
294 {
295   setlocale (LC_ALL, "");
296   if (mbsstr_trimmed_wordbounded (argv[1], argv[2]))
297     printf("found\n");
298   return 0;
299 }
300 #endif
301
302 #ifdef TEST2
303 # include <locale.h>
304 # include <stdio.h>
305 int
306 main (int argc, char *argv[])
307 {
308   setlocale (LC_ALL, "");
309   printf ("%s\n", proper_name_utf8 ("Franc,ois Pinard", "Fran\303\247ois Pinard"));
310   return 0;
311 }
312 #endif