Avoid calling iconv_open on each conversion.
[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 <iconv.h>
25 #include <errno.h>
26 #include "assertion.h"
27 #include "hmapx.h"
28 #include "hash-functions.h"
29
30 #include "i18n.h"
31
32 #include <localcharset.h>
33 #include "xstrndup.h"
34
35 #if HAVE_NL_LANGINFO
36 #include <langinfo.h>
37 #endif
38
39 static char *default_encoding;
40 static struct hmapx map;
41
42 /* A wrapper around iconv_open */
43 static iconv_t
44 create_iconv (const char* tocode, const char* fromcode)
45 {
46   iconv_t conv;
47   struct hmapx_node *node;
48   size_t hash ;
49   char *key = alloca (strlen (tocode) + strlen (fromcode) + 2);
50
51   strcpy (key, tocode);
52   strcat (key, "\n"); /* hopefully no encoding names contain '\n' */
53   strcat (key, fromcode);
54
55   hash = hsh_hash_string (key);
56
57   node = hmapx_first_with_hash (&map, hash);
58
59   if (!node)
60     {
61       conv = iconv_open (tocode, fromcode);
62
63       /* I don't think it's safe to translate this string or to use messaging
64          as the convertors have not yet been set up */
65       if ( (iconv_t) -1 == conv && 0 != strcmp (tocode, fromcode))
66         {
67           const int err = errno;
68           fprintf (stderr,
69                    "Warning: "
70                    "cannot create a convertor for \"%s\" to \"%s\": %s\n",
71                    fromcode, tocode, strerror (err));
72         }
73
74       hmapx_insert (&map, conv, hash);
75     }
76   else
77     {
78       conv = hmapx_node_data (node);
79     }
80
81   return conv;
82 }
83
84 /* Return a string based on TEXT converted according to HOW.
85    If length is not -1, then it must be the number of bytes in TEXT.
86    The returned string must be freed when no longer required.
87 */
88 char *
89 recode_string (const char *to, const char *from,
90                const char *text, int length)
91 {
92   char *outbuf = 0;
93   size_t outbufferlength;
94   size_t result;
95   char *op ;
96   size_t inbytes = 0;
97   size_t outbytes ;
98   iconv_t conv ;
99
100   /* FIXME: Need to ensure that this char is valid in the target encoding */
101   const char fallbackchar = '?';
102
103   if ( text == NULL )
104     return NULL;
105
106   if ( length == -1 )
107      length = strlen(text);
108
109
110   if (to == NULL)
111     to = default_encoding;
112
113   if (from == NULL)
114     from = default_encoding;
115
116   for ( outbufferlength = 1 ; outbufferlength != 0; outbufferlength <<= 1 )
117     if ( outbufferlength > length)
118       break;
119
120   outbuf = xmalloc(outbufferlength);
121   op = outbuf;
122
123   outbytes = outbufferlength;
124   inbytes = length;
125
126
127   conv = create_iconv (to, from);
128
129   do {
130     const char *ip = text;
131     result = iconv (conv, (ICONV_CONST char **) &text, &inbytes,
132                    &op, &outbytes);
133
134     if ( -1 == result )
135       {
136         int the_error = errno;
137
138         switch (the_error)
139           {
140           case EILSEQ:
141           case EINVAL:
142             if ( outbytes > 0 )
143               {
144                 *op++ = fallbackchar;
145                 outbytes--;
146                 text++;
147                 inbytes--;
148                 break;
149               }
150             /* Fall through */
151           case E2BIG:
152             free (outbuf);
153             outbufferlength <<= 1;
154             outbuf = xmalloc (outbufferlength);
155             op = outbuf;
156             outbytes = outbufferlength;
157             inbytes = length;
158             text = ip;
159             break;
160           default:
161             /* should never happen */
162             NOT_REACHED ();
163             break;
164           }
165       }
166   } while ( -1 == result );
167
168   if (outbytes == 0 )
169     {
170       char *const oldaddr = outbuf;
171       outbuf = xrealloc (outbuf, outbufferlength + 1);
172
173       op += (outbuf - oldaddr) ;
174     }
175
176   *op = '\0';
177
178   return outbuf;
179 }
180
181
182 void
183 i18n_init (void)
184 {
185   free (default_encoding);
186   default_encoding = strdup (locale_charset ());
187
188   hmapx_init (&map);
189 }
190
191
192 void
193 i18n_done (void)
194 {
195   struct hmapx_node *node;
196   iconv_t conv;
197   HMAPX_FOR_EACH (conv, node, &map)
198     iconv_close (conv);
199
200   hmapx_destroy (&map);
201
202   free (default_encoding);
203   default_encoding = NULL;
204 }
205
206
207
208
209 /* Return the system local's idea of the
210    decimal seperator character */
211 char
212 get_system_decimal (void)
213 {
214   char radix_char;
215
216   char *ol = strdup (setlocale (LC_NUMERIC, NULL));
217   setlocale (LC_NUMERIC, "");
218
219 #if HAVE_NL_LANGINFO
220   radix_char = nl_langinfo (RADIXCHAR)[0];
221 #else
222   {
223     char buf[10];
224     snprintf (buf, sizeof buf, "%f", 2.5);
225     radix_char = buf[1];
226   }
227 #endif
228
229   /* We MUST leave LC_NUMERIC untouched, since it would
230      otherwise interfere with data_{in,out} */
231   setlocale (LC_NUMERIC, ol);
232   free (ol);
233   return radix_char;
234 }
235