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