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