d9b42cbcca844da31d46630c78295b8276591d99
[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 /* Uses CONV to convert the INBYTES starting at IP into the OUTBYTES starting
102    at OP, and appends a null terminator to the output.
103
104    Returns true if successful, false if the output buffer is too small. */
105 static bool
106 try_recode (iconv_t conv,
107             const char *ip, size_t inbytes,
108             char *op, size_t outbytes)
109 {
110   /* FIXME: Need to ensure that this char is valid in the target encoding */
111   const char fallbackchar = '?';
112
113   /* Put the converter into the initial shift state, in case there was any
114      state information left over from its last usage. */
115   iconv (conv, NULL, 0, NULL, 0);
116
117   while (iconv (conv, (ICONV_CONST char **) &ip, &inbytes,
118                 &op, &outbytes) == -1)
119     switch (errno)
120       {
121       case EILSEQ:
122       case EINVAL:
123         if (outbytes == 0)
124           return false;
125
126         *op++ = fallbackchar;
127         outbytes--;
128         ip++;
129         inbytes--;
130         break;
131
132       case E2BIG:
133         return false;
134
135       default:
136         /* should never happen */
137         fprintf (stderr, "Character conversion error: %s\n", strerror (errno));
138         NOT_REACHED ();
139         break;
140       }
141
142   if (outbytes == 0)
143     return false;
144
145   *op = '\0';
146   return true;
147 }
148
149 /* Converts the string TEXT, which should be encoded in FROM-encoding, to a
150    dynamically allocated string in TO-encoding.  Any characters which cannot be
151    converted will be represented by '?'.
152
153    LENGTH should be the length of the string or -1, if null terminated.
154
155    The returned string will be allocated on POOL.
156
157    This function's behaviour differs from that of g_convert_with_fallback
158    provided by GLib.  The GLib function will fail (returns NULL) if any part of
159    the input string is not valid in the declared input encoding.  This function
160    however perseveres even in the presence of badly encoded input. */
161 char *
162 recode_string_pool (const char *to, const char *from,
163                     const char *text, int length, struct pool *pool)
164 {
165   size_t outbufferlength;
166   iconv_t conv ;
167
168   if ( text == NULL )
169     return NULL;
170
171   if ( length == -1 )
172      length = strlen(text);
173
174   if (to == NULL)
175     to = default_encoding;
176
177   if (from == NULL)
178     from = default_encoding;
179
180   conv = create_iconv (to, from);
181
182   if ( (iconv_t) -1 == conv )
183     return xstrdup (text);
184
185   for ( outbufferlength = 1 ; outbufferlength != 0; outbufferlength <<= 1 )
186     if ( outbufferlength > length)
187       {
188         char *output = pool_malloc (pool, outbufferlength);
189         if (try_recode (conv, text, length, output, outbufferlength))
190           return output;
191         pool_free (pool, output);
192       }
193
194   NOT_REACHED ();
195 }
196
197 void
198 i18n_init (void)
199 {
200 #if ENABLE_NLS
201   setlocale (LC_CTYPE, "");
202 #ifdef LC_MESSAGES
203   setlocale (LC_MESSAGES, "");
204 #endif
205 #if HAVE_LC_PAPER
206   setlocale (LC_PAPER, "");
207 #endif
208   bindtextdomain (PACKAGE, relocate(locale_dir));
209   textdomain (PACKAGE);
210 #endif /* ENABLE_NLS */
211
212   assert (default_encoding == NULL);
213   default_encoding = xstrdup (locale_charset ());
214
215   hmapx_init (&map);
216 }
217
218
219 const char *
220 get_default_encoding (void)
221 {
222   return default_encoding;
223 }
224
225 void
226 set_default_encoding (const char *enc)
227 {
228   free (default_encoding);
229   default_encoding = xstrdup (enc);
230 }
231
232
233 /* Attempts to set the encoding from a locale name
234    returns true if successfull.
235    This function does not (should not!) alter the current locale.
236 */
237 bool
238 set_encoding_from_locale (const char *loc)
239 {
240   bool ok = true;
241   char *c_encoding;
242   char *loc_encoding;
243   char *tmp = xstrdup (setlocale (LC_CTYPE, NULL));
244
245   setlocale (LC_CTYPE, "C");
246   c_encoding = xstrdup (locale_charset ());
247
248   setlocale (LC_CTYPE, loc);
249   loc_encoding = xstrdup (locale_charset ());
250
251
252   if ( 0 == strcmp (loc_encoding, c_encoding))
253     {
254       ok = false;
255     }
256
257
258   setlocale (LC_CTYPE, tmp);
259
260   free (tmp);
261
262   if (ok)
263     {
264       free (default_encoding);
265       default_encoding = loc_encoding;
266     }
267   else
268     free (loc_encoding);
269
270   free (c_encoding);
271
272   return ok;
273 }
274
275 void
276 i18n_done (void)
277 {
278   struct hmapx_node *node;
279   struct converter *cvtr;
280
281   HMAPX_FOR_EACH (cvtr, node, &map)
282     {
283       free (cvtr->tocode);
284       free (cvtr->fromcode);
285       iconv_close (cvtr->conv);
286       free (cvtr);
287     }
288
289   hmapx_destroy (&map);
290
291   free (default_encoding);
292   default_encoding = NULL;
293 }
294
295
296
297 bool
298 valid_encoding (const char *enc)
299 {
300   iconv_t conv = iconv_open ("UTF8", enc);
301
302   if ( conv == (iconv_t) -1)
303     return false;
304
305   iconv_close (conv);
306
307   return true;
308 }
309
310
311 /* Return the system local's idea of the
312    decimal seperator character */
313 char
314 get_system_decimal (void)
315 {
316   char radix_char;
317
318   char *ol = xstrdup (setlocale (LC_NUMERIC, NULL));
319   setlocale (LC_NUMERIC, "");
320
321 #if HAVE_NL_LANGINFO
322   radix_char = nl_langinfo (RADIXCHAR)[0];
323 #else
324   {
325     char buf[10];
326     snprintf (buf, sizeof buf, "%f", 2.5);
327     radix_char = buf[1];
328   }
329 #endif
330
331   /* We MUST leave LC_NUMERIC untouched, since it would
332      otherwise interfere with data_{in,out} */
333   setlocale (LC_NUMERIC, ol);
334   free (ol);
335   return radix_char;
336 }
337