Expanded comments to src/libpspp/i18n.[ch]
[pspp-builds.git] / src / libpspp / i18n.c
1 /* PSPP - a program for statistical analysis.
2    Copyright (C) 2006, 2009 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     const char *tocode;
47     const 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 instead of 
91    in a pool.  It is the caller's responsibility to free the returned value. */
92 char *
93 recode_string (const char *to, const char *from,
94                const char *text, int length)
95 {
96   return recode_string_pool (to, from, text, length, NULL);
97 }
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
103 be 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 provided
110 by GLib.  The GLib function will fail (returns NULL) if any part of the input
111 string is not valid in the declared input encoding.  This function however perseveres
112 even in the presence of badly encoded input.
113 */
114 char *
115 recode_string_pool (const char *to, const char *from,
116                const char *text, int length, struct pool *pool)
117 {
118   char *outbuf = 0;
119   size_t outbufferlength;
120   size_t result;
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   for ( outbufferlength = 1 ; outbufferlength != 0; outbufferlength <<= 1 )
142     if ( outbufferlength > length)
143       break;
144
145   outbuf = pool_malloc (pool, outbufferlength);
146   op = outbuf;
147
148   outbytes = outbufferlength;
149   inbytes = length;
150
151
152   conv = create_iconv (to, from);
153
154   if ( (iconv_t) -1 == conv )
155         return xstrdup (text);
156
157   do {
158     const char *ip = text;
159     result = iconv (conv, (ICONV_CONST char **) &text, &inbytes,
160                    &op, &outbytes);
161
162     if ( -1 == result )
163       {
164         int the_error = errno;
165
166         switch (the_error)
167           {
168           case EILSEQ:
169           case EINVAL:
170             if ( outbytes > 0 )
171               {
172                 *op++ = fallbackchar;
173                 outbytes--;
174                 text++;
175                 inbytes--;
176                 break;
177               }
178             /* Fall through */
179           case E2BIG:
180             free (outbuf);
181             outbufferlength <<= 1;
182             outbuf = pool_malloc (pool, outbufferlength);
183             op = outbuf;
184             outbytes = outbufferlength;
185             inbytes = length;
186             text = ip;
187             break;
188           default:
189             /* should never happen */
190             fprintf (stderr, "Character conversion error: %s\n", strerror (the_error));
191             NOT_REACHED ();
192             break;
193           }
194       }
195   } while ( -1 == result );
196
197   if (outbytes == 0 )
198     {
199       char *const oldaddr = outbuf;
200       outbuf = pool_realloc (pool, outbuf, outbufferlength + 1);
201
202       op += (outbuf - oldaddr) ;
203     }
204
205   *op = '\0';
206
207   return outbuf;
208 }
209
210
211 void
212 i18n_init (void)
213 {
214 #if ENABLE_NLS
215   setlocale (LC_CTYPE, "");
216 #ifdef LC_MESSAGES
217   setlocale (LC_MESSAGES, "");
218 #endif
219 #if HAVE_LC_PAPER
220   setlocale (LC_PAPER, "");
221 #endif
222   bindtextdomain (PACKAGE, relocate(locale_dir));
223   textdomain (PACKAGE);
224 #endif /* ENABLE_NLS */
225
226   assert (default_encoding == NULL);
227   default_encoding = xstrdup (locale_charset ());
228
229   hmapx_init (&map);
230 }
231
232
233 const char *
234 get_default_encoding (void)
235 {
236   return default_encoding;
237 }
238
239 void
240 set_default_encoding (const char *enc)
241 {
242   free (default_encoding);
243   default_encoding = xstrdup (enc);
244 }
245
246
247 /* Attempts to set the encoding from a locale name
248    returns true if successfull.
249    This function does not (should not!) alter the current locale.
250 */
251 bool
252 set_encoding_from_locale (const char *loc)
253 {
254   bool ok = true;
255   char *c_encoding;
256   char *loc_encoding;
257   char *tmp = xstrdup (setlocale (LC_CTYPE, NULL));
258
259   setlocale (LC_CTYPE, "C");
260   c_encoding = xstrdup (locale_charset ());
261
262   setlocale (LC_CTYPE, loc);
263   loc_encoding = xstrdup (locale_charset ());
264
265
266   if ( 0 == strcmp (loc_encoding, c_encoding))
267     {
268       ok = false;
269     }
270
271
272   setlocale (LC_CTYPE, tmp);
273
274   free (tmp);
275
276   if (ok)
277     {
278       free (default_encoding);
279       default_encoding = loc_encoding;
280     }
281   else
282     free (loc_encoding);
283
284   free (c_encoding);
285
286   return ok;
287 }
288
289 void
290 i18n_done (void)
291 {
292   struct hmapx_node *node;
293   struct converter *cvtr;
294   HMAPX_FOR_EACH (cvtr, node, &map)
295     {
296       iconv_close (cvtr->conv);
297       free (cvtr);
298     }
299
300   hmapx_destroy (&map);
301
302   free (default_encoding);
303   default_encoding = NULL;
304 }
305
306
307
308 bool
309 valid_encoding (const char *enc)
310 {
311   iconv_t conv = iconv_open ("UTF8", enc);
312
313   if ( conv == (iconv_t) -1)
314     return false;
315
316   iconv_close (conv);
317
318   return true;
319 }
320
321
322 /* Return the system local's idea of the
323    decimal seperator character */
324 char
325 get_system_decimal (void)
326 {
327   char radix_char;
328
329   char *ol = xstrdup (setlocale (LC_NUMERIC, NULL));
330   setlocale (LC_NUMERIC, "");
331
332 #if HAVE_NL_LANGINFO
333   radix_char = nl_langinfo (RADIXCHAR)[0];
334 #else
335   {
336     char buf[10];
337     snprintf (buf, sizeof buf, "%f", 2.5);
338     radix_char = buf[1];
339   }
340 #endif
341
342   /* We MUST leave LC_NUMERIC untouched, since it would
343      otherwise interfere with data_{in,out} */
344   setlocale (LC_NUMERIC, ol);
345   free (ol);
346   return radix_char;
347 }
348