Make create_iconv() properly distinguish converters by name.
[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 "assertion.h"
28 #include "hmapx.h"
29 #include "hash-functions.h"
30
31 #include "i18n.h"
32
33 #include "version.h"
34
35 #include <localcharset.h>
36 #include "xstrndup.h"
37
38 #if HAVE_NL_LANGINFO
39 #include <langinfo.h>
40 #endif
41
42 struct converter
43   {
44     const char *tocode;
45     const char *fromcode;
46     iconv_t conv;
47   };
48
49 static char *default_encoding;
50 static struct hmapx map;
51
52 /* A wrapper around iconv_open */
53 static iconv_t
54 create_iconv (const char* tocode, const char* fromcode)
55 {
56   size_t hash;
57   struct hmapx_node *node;
58   struct converter *converter;
59
60   hash = hsh_hash_string (tocode) ^ hsh_hash_string (fromcode);
61   HMAPX_FOR_EACH_WITH_HASH (converter, node, hash, &map)
62     if (!strcmp (tocode, converter->tocode)
63         && !strcmp (fromcode, converter->fromcode))
64       return converter->conv;
65
66   converter = xmalloc (sizeof *converter);
67   converter->tocode = xstrdup (tocode);
68   converter->fromcode = xstrdup (fromcode);
69   converter->conv = iconv_open (tocode, fromcode);
70   hmapx_insert (&map, converter, hash);
71   
72   /* I don't think it's safe to translate this string or to use messaging
73      as the convertors have not yet been set up */
74   if ( (iconv_t) -1 == converter->conv && 0 != strcmp (tocode, fromcode))
75     {
76       const int err = errno;
77       fprintf (stderr,
78                "Warning: "
79                "cannot create a convertor for \"%s\" to \"%s\": %s\n",
80                fromcode, tocode, strerror (err));
81     }
82
83   return converter->conv;
84 }
85
86 /* Return a string based on TEXT converted according to HOW.
87    If length is not -1, then it must be the number of bytes in TEXT.
88    The returned string must be freed when no longer required.
89 */
90 char *
91 recode_string (const char *to, const char *from,
92                const char *text, int length)
93 {
94   char *outbuf = 0;
95   size_t outbufferlength;
96   size_t result;
97   char *op ;
98   size_t inbytes = 0;
99   size_t outbytes ;
100   iconv_t conv ;
101
102   /* FIXME: Need to ensure that this char is valid in the target encoding */
103   const char fallbackchar = '?';
104
105   if ( text == NULL )
106     return NULL;
107
108   if ( length == -1 )
109      length = strlen(text);
110
111
112   if (to == NULL)
113     to = default_encoding;
114
115   if (from == NULL)
116     from = default_encoding;
117
118   for ( outbufferlength = 1 ; outbufferlength != 0; outbufferlength <<= 1 )
119     if ( outbufferlength > length)
120       break;
121
122   outbuf = xmalloc(outbufferlength);
123   op = outbuf;
124
125   outbytes = outbufferlength;
126   inbytes = length;
127
128
129   conv = create_iconv (to, from);
130
131   do {
132     const char *ip = text;
133     result = iconv (conv, (ICONV_CONST char **) &text, &inbytes,
134                    &op, &outbytes);
135
136     if ( -1 == result )
137       {
138         int the_error = errno;
139
140         switch (the_error)
141           {
142           case EILSEQ:
143           case EINVAL:
144             if ( outbytes > 0 )
145               {
146                 *op++ = fallbackchar;
147                 outbytes--;
148                 text++;
149                 inbytes--;
150                 break;
151               }
152             /* Fall through */
153           case E2BIG:
154             free (outbuf);
155             outbufferlength <<= 1;
156             outbuf = xmalloc (outbufferlength);
157             op = outbuf;
158             outbytes = outbufferlength;
159             inbytes = length;
160             text = ip;
161             break;
162           default:
163             /* should never happen */
164             NOT_REACHED ();
165             break;
166           }
167       }
168   } while ( -1 == result );
169
170   if (outbytes == 0 )
171     {
172       char *const oldaddr = outbuf;
173       outbuf = xrealloc (outbuf, outbufferlength + 1);
174
175       op += (outbuf - oldaddr) ;
176     }
177
178   *op = '\0';
179
180   return outbuf;
181 }
182
183
184 void
185 i18n_init (void)
186 {
187 #if ENABLE_NLS
188   setlocale (LC_CTYPE, "");
189 #if HAVE_LC_MESSAGES
190   setlocale (LC_MESSAGES, "");
191 #endif
192 #if HAVE_LC_PAPER
193   setlocale (LC_PAPER, "");
194 #endif
195   bindtextdomain (PACKAGE, locale_dir);
196   textdomain (PACKAGE);
197 #endif /* ENABLE_NLS */
198
199   assert (default_encoding == NULL);
200   default_encoding = xstrdup (locale_charset ());
201
202   hmapx_init (&map);
203 }
204
205
206 const char *
207 get_default_encoding (void)
208 {
209   return default_encoding;
210 }
211
212 void
213 set_default_encoding (const char *enc)
214 {
215   free (default_encoding);
216   default_encoding = xstrdup (enc);
217 }
218
219
220 /* Attempts to set the encoding from a locale name
221    returns true if successfull.
222    This function does not (should not!) alter the current locale.
223 */
224 bool
225 set_encoding_from_locale (const char *loc)
226 {
227   bool ok = true;
228   char *c_encoding;
229   char *loc_encoding;
230   char *tmp = xstrdup (setlocale (LC_CTYPE, NULL));
231
232   setlocale (LC_CTYPE, "C");
233   c_encoding = xstrdup (locale_charset ());
234
235   setlocale (LC_CTYPE, loc);
236   loc_encoding = xstrdup (locale_charset ());
237
238
239   if ( 0 == strcmp (loc_encoding, c_encoding))
240     {
241       ok = false;
242     }
243
244
245   setlocale (LC_CTYPE, tmp);
246
247   free (tmp);
248
249   if (ok)
250     {
251       free (default_encoding);
252       default_encoding = loc_encoding;
253     }
254   else
255     free (loc_encoding);
256
257   free (c_encoding);
258
259   return ok;
260 }
261
262 void
263 i18n_done (void)
264 {
265   struct hmapx_node *node;
266   iconv_t conv;
267   HMAPX_FOR_EACH (conv, node, &map)
268     iconv_close (conv);
269
270   hmapx_destroy (&map);
271
272   free (default_encoding);
273   default_encoding = NULL;
274 }
275
276
277
278 bool
279 valid_encoding (const char *enc)
280 {
281   iconv_t conv = iconv_open ("UTF8", enc);
282
283   if ( conv == (iconv_t) -1)
284     return false;
285
286   iconv_close (conv);
287
288   return true;
289 }
290
291
292 /* Return the system local's idea of the
293    decimal seperator character */
294 char
295 get_system_decimal (void)
296 {
297   char radix_char;
298
299   char *ol = xstrdup (setlocale (LC_NUMERIC, NULL));
300   setlocale (LC_NUMERIC, "");
301
302 #if HAVE_NL_LANGINFO
303   radix_char = nl_langinfo (RADIXCHAR)[0];
304 #else
305   {
306     char buf[10];
307     snprintf (buf, sizeof buf, "%f", 2.5);
308     radix_char = buf[1];
309   }
310 #endif
311
312   /* We MUST leave LC_NUMERIC untouched, since it would
313      otherwise interfere with data_{in,out} */
314   setlocale (LC_NUMERIC, ol);
315   free (ol);
316   return radix_char;
317 }
318