fa9f29c5835e561354f92877581f0a7ab6f10705
[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 *ip;
121   char *op ;
122   size_t inbytes = 0;
123   size_t outbytes ;
124   iconv_t conv ;
125
126   /* FIXME: Need to ensure that this char is valid in the target encoding */
127   const char fallbackchar = '?';
128
129   if ( text == NULL )
130     return NULL;
131
132   if ( length == -1 )
133      length = strlen(text);
134
135   if (to == NULL)
136     to = default_encoding;
137
138   if (from == NULL)
139     from = default_encoding;
140
141   conv = create_iconv (to, from);
142
143   if ( (iconv_t) -1 == conv )
144     return xstrdup (text);
145
146   /* Put the converter into the initial shift state, in case there was any
147      state information left over from its last usage. */
148   iconv (conv, NULL, 0, NULL, 0);
149
150   for ( outbufferlength = 1 ; outbufferlength != 0; outbufferlength <<= 1 )
151     if ( outbufferlength > length)
152       break;
153
154   ip = text;
155
156   outbuf = pool_malloc (pool, outbufferlength);
157   op = outbuf;
158
159   outbytes = outbufferlength;
160   inbytes = length;
161
162
163   do {
164     result = iconv (conv, (ICONV_CONST char **) &ip, &inbytes,
165                    &op, &outbytes);
166
167     if ( -1 == result )
168       {
169         int the_error = errno;
170
171         switch (the_error)
172           {
173           case EILSEQ:
174           case EINVAL:
175             if ( outbytes > 0 )
176               {
177                 *op++ = fallbackchar;
178                 outbytes--;
179                 text++;
180                 inbytes--;
181                 break;
182               }
183             /* Fall through */
184           case E2BIG:
185             iconv (conv, NULL, 0, NULL, 0);
186             pool_free (pool, outbuf);
187             outbufferlength <<= 1;
188             outbuf = pool_malloc (pool, outbufferlength);
189             op = outbuf;
190             outbytes = outbufferlength;
191             inbytes = length;
192             ip = text;
193             break;
194           default:
195             /* should never happen */
196             fprintf (stderr, "Character conversion error: %s\n",
197                      strerror (the_error));
198             NOT_REACHED ();
199             break;
200           }
201       }
202   } while ( -1 == result );
203
204   if (outbytes == 0 )
205     {
206       char *const oldaddr = outbuf;
207       outbuf = pool_realloc (pool, outbuf, outbufferlength + 1);
208
209       op += (outbuf - oldaddr) ;
210     }
211
212   *op = '\0';
213
214   return outbuf;
215 }
216
217
218 void
219 i18n_init (void)
220 {
221 #if ENABLE_NLS
222   setlocale (LC_CTYPE, "");
223 #ifdef LC_MESSAGES
224   setlocale (LC_MESSAGES, "");
225 #endif
226 #if HAVE_LC_PAPER
227   setlocale (LC_PAPER, "");
228 #endif
229   bindtextdomain (PACKAGE, relocate(locale_dir));
230   textdomain (PACKAGE);
231 #endif /* ENABLE_NLS */
232
233   assert (default_encoding == NULL);
234   default_encoding = xstrdup (locale_charset ());
235
236   hmapx_init (&map);
237 }
238
239
240 const char *
241 get_default_encoding (void)
242 {
243   return default_encoding;
244 }
245
246 void
247 set_default_encoding (const char *enc)
248 {
249   free (default_encoding);
250   default_encoding = xstrdup (enc);
251 }
252
253
254 /* Attempts to set the encoding from a locale name
255    returns true if successfull.
256    This function does not (should not!) alter the current locale.
257 */
258 bool
259 set_encoding_from_locale (const char *loc)
260 {
261   bool ok = true;
262   char *c_encoding;
263   char *loc_encoding;
264   char *tmp = xstrdup (setlocale (LC_CTYPE, NULL));
265
266   setlocale (LC_CTYPE, "C");
267   c_encoding = xstrdup (locale_charset ());
268
269   setlocale (LC_CTYPE, loc);
270   loc_encoding = xstrdup (locale_charset ());
271
272
273   if ( 0 == strcmp (loc_encoding, c_encoding))
274     {
275       ok = false;
276     }
277
278
279   setlocale (LC_CTYPE, tmp);
280
281   free (tmp);
282
283   if (ok)
284     {
285       free (default_encoding);
286       default_encoding = loc_encoding;
287     }
288   else
289     free (loc_encoding);
290
291   free (c_encoding);
292
293   return ok;
294 }
295
296 void
297 i18n_done (void)
298 {
299   struct hmapx_node *node;
300   struct converter *cvtr;
301
302   HMAPX_FOR_EACH (cvtr, node, &map)
303     {
304       free (cvtr->tocode);
305       free (cvtr->fromcode);
306       iconv_close (cvtr->conv);
307       free (cvtr);
308     }
309
310   hmapx_destroy (&map);
311
312   free (default_encoding);
313   default_encoding = NULL;
314 }
315
316
317
318 bool
319 valid_encoding (const char *enc)
320 {
321   iconv_t conv = iconv_open ("UTF8", enc);
322
323   if ( conv == (iconv_t) -1)
324     return false;
325
326   iconv_close (conv);
327
328   return true;
329 }
330
331
332 /* Return the system local's idea of the
333    decimal seperator character */
334 char
335 get_system_decimal (void)
336 {
337   char radix_char;
338
339   char *ol = xstrdup (setlocale (LC_NUMERIC, NULL));
340   setlocale (LC_NUMERIC, "");
341
342 #if HAVE_NL_LANGINFO
343   radix_char = nl_langinfo (RADIXCHAR)[0];
344 #else
345   {
346     char buf[10];
347     snprintf (buf, sizeof buf, "%f", 2.5);
348     radix_char = buf[1];
349   }
350 #endif
351
352   /* We MUST leave LC_NUMERIC untouched, since it would
353      otherwise interfere with data_{in,out} */
354   setlocale (LC_NUMERIC, ol);
355   free (ol);
356   return radix_char;
357 }
358