i18n: Ensure that every recoding starts from the initial shift state.
[pspp] / src / libpspp / i18n.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2006, 2009, 2010 Free Software Foundation, Inc.
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18 #include <xalloc.h>
19 #include <assert.h>
20 #include <locale.h>
21 #include <stdlib.h>
22 #include <stdio.h>
23 #include <string.h>
24 #include <libintl.h>
25 #include <iconv.h>
26 #include <errno.h>
27 #include <relocatable.h>
28 #include "assertion.h"
29 #include "hmapx.h"
30 #include "hash-functions.h"
31 #include "pool.h"
32
33 #include "i18n.h"
34
35 #include "version.h"
36
37 #include <localcharset.h>
38 #include "xstrndup.h"
39
40 #if HAVE_NL_LANGINFO
41 #include <langinfo.h>
42 #endif
43
44 struct converter
45  {
46     char *tocode;
47     char *fromcode;
48     iconv_t conv;
49   };
50
51 static char *default_encoding;
52 static struct hmapx map;
53
54 /* A wrapper around iconv_open */
55 static iconv_t
56 create_iconv (const char* tocode, const char* fromcode)
57 {
58   size_t hash;
59   struct hmapx_node *node;
60   struct converter *converter;
61   assert (fromcode);
62
63   hash = hash_string (tocode, hash_string (fromcode, 0));
64   HMAPX_FOR_EACH_WITH_HASH (converter, node, hash, &map)
65     if (!strcmp (tocode, converter->tocode)
66         && !strcmp (fromcode, converter->fromcode))
67       return converter->conv;
68
69   converter = xmalloc (sizeof *converter);
70   converter->tocode = xstrdup (tocode);
71   converter->fromcode = xstrdup (fromcode);
72   converter->conv = iconv_open (tocode, fromcode);
73   hmapx_insert (&map, converter, hash);
74
75   /* I don't think it's safe to translate this string or to use messaging
76      as the converters have not yet been set up */
77   if ( (iconv_t) -1 == converter->conv && 0 != strcmp (tocode, fromcode))
78     {
79       const int err = errno;
80       fprintf (stderr,
81                "Warning: "
82                "cannot create a converter for `%s' to `%s': %s\n",
83                fromcode, tocode, strerror (err));
84     }
85
86   return converter->conv;
87 }
88
89
90 /* Similar to recode_string_pool, but allocates the returned value on the heap
91    instead of in a pool.  It is the caller's responsibility to free the
92    returned value. */
93 char *
94 recode_string (const char *to, const char *from,
95                const char *text, int length)
96 {
97   return recode_string_pool (to, from, text, length, NULL);
98 }
99
100
101 /* Converts the string TEXT, which should be encoded in FROM-encoding, to a
102    dynamically allocated string in TO-encoding.  Any characters which cannot be
103    converted will be represented by '?'.
104
105    LENGTH should be the length of the string or -1, if null terminated.
106
107    The returned string will be allocated on POOL.
108
109    This function's behaviour differs from that of g_convert_with_fallback
110    provided by GLib.  The GLib function will fail (returns NULL) if any part of
111    the input string is not valid in the declared input encoding.  This function
112    however perseveres even in the presence of badly encoded input. */
113 char *
114 recode_string_pool (const char *to, const char *from,
115                     const char *text, int length, struct pool *pool)
116 {
117   char *outbuf = 0;
118   size_t outbufferlength;
119   size_t result;
120   char *op ;
121   size_t inbytes = 0;
122   size_t outbytes ;
123   iconv_t conv ;
124
125   /* FIXME: Need to ensure that this char is valid in the target encoding */
126   const char fallbackchar = '?';
127
128   if ( text == NULL )
129     return NULL;
130
131   if ( length == -1 )
132      length = strlen(text);
133
134   if (to == NULL)
135     to = default_encoding;
136
137   if (from == NULL)
138     from = default_encoding;
139
140   conv = create_iconv (to, from);
141
142   if ( (iconv_t) -1 == conv )
143     return xstrdup (text);
144
145   /* Put the converter into the initial shift state, in case there was any
146      state information left over from its last usage. */
147   iconv (conv, NULL, 0, NULL, 0);
148
149   for ( outbufferlength = 1 ; outbufferlength != 0; outbufferlength <<= 1 )
150     if ( outbufferlength > length)
151       break;
152
153   outbuf = pool_malloc (pool, outbufferlength);
154   op = outbuf;
155
156   outbytes = outbufferlength;
157   inbytes = length;
158
159
160   do {
161     const char *ip = text;
162     result = iconv (conv, (ICONV_CONST char **) &text, &inbytes,
163                    &op, &outbytes);
164
165     if ( -1 == result )
166       {
167         int the_error = errno;
168
169         switch (the_error)
170           {
171           case EILSEQ:
172           case EINVAL:
173             if ( outbytes > 0 )
174               {
175                 *op++ = fallbackchar;
176                 outbytes--;
177                 text++;
178                 inbytes--;
179                 break;
180               }
181             /* Fall through */
182           case E2BIG:
183             iconv (conv, NULL, 0, NULL, 0);
184             pool_free (pool, outbuf);
185             outbufferlength <<= 1;
186             outbuf = pool_malloc (pool, outbufferlength);
187             op = outbuf;
188             outbytes = outbufferlength;
189             inbytes = length;
190             text = ip;
191             break;
192           default:
193             /* should never happen */
194             fprintf (stderr, "Character conversion error: %s\n",
195                      strerror (the_error));
196             NOT_REACHED ();
197             break;
198           }
199       }
200   } while ( -1 == result );
201
202   if (outbytes == 0 )
203     {
204       char *const oldaddr = outbuf;
205       outbuf = pool_realloc (pool, outbuf, outbufferlength + 1);
206
207       op += (outbuf - oldaddr) ;
208     }
209
210   *op = '\0';
211
212   return outbuf;
213 }
214
215
216 void
217 i18n_init (void)
218 {
219 #if ENABLE_NLS
220   setlocale (LC_CTYPE, "");
221 #ifdef LC_MESSAGES
222   setlocale (LC_MESSAGES, "");
223 #endif
224 #if HAVE_LC_PAPER
225   setlocale (LC_PAPER, "");
226 #endif
227   bindtextdomain (PACKAGE, relocate(locale_dir));
228   textdomain (PACKAGE);
229 #endif /* ENABLE_NLS */
230
231   assert (default_encoding == NULL);
232   default_encoding = xstrdup (locale_charset ());
233
234   hmapx_init (&map);
235 }
236
237
238 const char *
239 get_default_encoding (void)
240 {
241   return default_encoding;
242 }
243
244 void
245 set_default_encoding (const char *enc)
246 {
247   free (default_encoding);
248   default_encoding = xstrdup (enc);
249 }
250
251
252 /* Attempts to set the encoding from a locale name
253    returns true if successfull.
254    This function does not (should not!) alter the current locale.
255 */
256 bool
257 set_encoding_from_locale (const char *loc)
258 {
259   bool ok = true;
260   char *c_encoding;
261   char *loc_encoding;
262   char *tmp = xstrdup (setlocale (LC_CTYPE, NULL));
263
264   setlocale (LC_CTYPE, "C");
265   c_encoding = xstrdup (locale_charset ());
266
267   setlocale (LC_CTYPE, loc);
268   loc_encoding = xstrdup (locale_charset ());
269
270
271   if ( 0 == strcmp (loc_encoding, c_encoding))
272     {
273       ok = false;
274     }
275
276
277   setlocale (LC_CTYPE, tmp);
278
279   free (tmp);
280
281   if (ok)
282     {
283       free (default_encoding);
284       default_encoding = loc_encoding;
285     }
286   else
287     free (loc_encoding);
288
289   free (c_encoding);
290
291   return ok;
292 }
293
294 void
295 i18n_done (void)
296 {
297   struct hmapx_node *node;
298   struct converter *cvtr;
299
300   HMAPX_FOR_EACH (cvtr, node, &map)
301     {
302       free (cvtr->tocode);
303       free (cvtr->fromcode);
304       iconv_close (cvtr->conv);
305       free (cvtr);
306     }
307
308   hmapx_destroy (&map);
309
310   free (default_encoding);
311   default_encoding = NULL;
312 }
313
314
315
316 bool
317 valid_encoding (const char *enc)
318 {
319   iconv_t conv = iconv_open ("UTF8", enc);
320
321   if ( conv == (iconv_t) -1)
322     return false;
323
324   iconv_close (conv);
325
326   return true;
327 }
328
329
330 /* Return the system local's idea of the
331    decimal seperator character */
332 char
333 get_system_decimal (void)
334 {
335   char radix_char;
336
337   char *ol = xstrdup (setlocale (LC_NUMERIC, NULL));
338   setlocale (LC_NUMERIC, "");
339
340 #if HAVE_NL_LANGINFO
341   radix_char = nl_langinfo (RADIXCHAR)[0];
342 #else
343   {
344     char buf[10];
345     snprintf (buf, sizeof buf, "%f", 2.5);
346     radix_char = buf[1];
347   }
348 #endif
349
350   /* We MUST leave LC_NUMERIC untouched, since it would
351      otherwise interfere with data_{in,out} */
352   setlocale (LC_NUMERIC, ol);
353   free (ol);
354   return radix_char;
355 }
356