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