i18n: New function recode_substring_pool().
[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/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 (&out, text);
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 #if ENABLE_NLS
233   setlocale (LC_CTYPE, "");
234 #ifdef LC_MESSAGES
235   setlocale (LC_MESSAGES, "");
236 #endif
237 #if HAVE_LC_PAPER
238   setlocale (LC_PAPER, "");
239 #endif
240   bindtextdomain (PACKAGE, relocate(locale_dir));
241   textdomain (PACKAGE);
242 #endif /* ENABLE_NLS */
243
244   assert (default_encoding == NULL);
245   default_encoding = xstrdup (locale_charset ());
246
247   hmapx_init (&map);
248 }
249
250
251 const char *
252 get_default_encoding (void)
253 {
254   return default_encoding;
255 }
256
257 void
258 set_default_encoding (const char *enc)
259 {
260   free (default_encoding);
261   default_encoding = xstrdup (enc);
262 }
263
264
265 /* Attempts to set the encoding from a locale name
266    returns true if successfull.
267    This function does not (should not!) alter the current locale.
268 */
269 bool
270 set_encoding_from_locale (const char *loc)
271 {
272   bool ok = true;
273   char *c_encoding;
274   char *loc_encoding;
275   char *tmp = xstrdup (setlocale (LC_CTYPE, NULL));
276
277   setlocale (LC_CTYPE, "C");
278   c_encoding = xstrdup (locale_charset ());
279
280   setlocale (LC_CTYPE, loc);
281   loc_encoding = xstrdup (locale_charset ());
282
283
284   if ( 0 == strcmp (loc_encoding, c_encoding))
285     {
286       ok = false;
287     }
288
289
290   setlocale (LC_CTYPE, tmp);
291
292   free (tmp);
293
294   if (ok)
295     {
296       free (default_encoding);
297       default_encoding = loc_encoding;
298     }
299   else
300     free (loc_encoding);
301
302   free (c_encoding);
303
304   return ok;
305 }
306
307 void
308 i18n_done (void)
309 {
310   struct hmapx_node *node;
311   struct converter *cvtr;
312
313   HMAPX_FOR_EACH (cvtr, node, &map)
314     {
315       free (cvtr->tocode);
316       free (cvtr->fromcode);
317       iconv_close (cvtr->conv);
318       free (cvtr);
319     }
320
321   hmapx_destroy (&map);
322
323   free (default_encoding);
324   default_encoding = NULL;
325 }
326
327
328
329 bool
330 valid_encoding (const char *enc)
331 {
332   iconv_t conv = iconv_open (UTF8, enc);
333
334   if ( conv == (iconv_t) -1)
335     return false;
336
337   iconv_close (conv);
338
339   return true;
340 }
341
342
343 /* Return the system local's idea of the
344    decimal seperator character */
345 char
346 get_system_decimal (void)
347 {
348   char radix_char;
349
350   char *ol = xstrdup (setlocale (LC_NUMERIC, NULL));
351   setlocale (LC_NUMERIC, "");
352
353 #if HAVE_NL_LANGINFO
354   radix_char = nl_langinfo (RADIXCHAR)[0];
355 #else
356   {
357     char buf[10];
358     snprintf (buf, sizeof buf, "%f", 2.5);
359     radix_char = buf[1];
360   }
361 #endif
362
363   /* We MUST leave LC_NUMERIC untouched, since it would
364      otherwise interfere with data_{in,out} */
365   setlocale (LC_NUMERIC, ol);
366   free (ol);
367   return radix_char;
368 }
369