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