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