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