Change ERROR_OTHER to ERROR_FAILED, add some dummy marking with _().
[platform/upstream/glib.git] / gconvert.c
1 /* GLIB - Library of useful routines for C programming
2  *
3  * gconvert.c: Convert between character sets using iconv
4  * Copyright Red Hat Inc., 2000
5  * Authors: Havoc Pennington <hp@redhat.com>, Owen Taylor <otaylor@redhat.com
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the
19  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20  * Boston, MA 02111-1307, USA.
21  */
22
23 #include <iconv.h>
24 #include <errno.h>
25 #include <string.h>
26
27 #include "glib.h"
28
29 #define _(s) (s)
30
31 GQuark 
32 g_convert_error_quark()
33 {
34   static GQuark quark;
35   if (!quark)
36     quark = g_quark_from_static_string ("g_convert_error");
37   return quark;
38 }
39
40 static iconv_t
41 open_converter (const gchar *to_codeset,
42                 const gchar *from_codeset,
43                 GError     **error)
44 {
45   iconv_t cd = iconv_open (to_codeset, from_codeset);
46
47   if (cd == (iconv_t) -1)
48     {
49       /* Something went wrong.  */
50       if (errno == EINVAL)
51         g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_NO_CONVERSION,
52                      _("Conversion from character set `%s' to `%s' is not supported"),
53                      from_codeset, to_codeset);
54       else
55         g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_FAILED,
56                      _("Could not open converter from `%s' to `%s': %s"),
57                      from_codeset, to_codeset, strerror (errno));
58     }
59
60   return cd;
61
62 }
63
64 /**
65  * g_convert:
66  * @str:           the string to convert
67  * @len:           the length of the string
68  * @to_codeset:    name of character set into which to convert @str
69  * @from_codeset:  character set of @str.
70  * @bytes_read:    location to store the number of bytes in the
71  *                 input string that were successfully converted, or %NULL.
72  *                 Even if the conversion was succesful, this may be 
73  *                 less than len if there were partial characters
74  *                 at the end of the input. If the error
75  *                 G_CONVERT_ERROR_ILLEGAL_SEQUENCE occurs, the value
76  *                 stored will the byte fofset after the last valid
77  *                 input sequence.
78  * @bytes_written: the stored in the output buffer (not including the
79  *                 terminating nul.
80  * @error:         location to store the error occuring, or %NULL to ignore
81  *                 errors. Any of the errors in #GConvertError may occur.
82  *
83  * Convert a string from one character set to another.
84  *
85  * Return value: If the conversion was successful, a newly allocated
86  *               NUL-terminated string, which must be freed with
87  *               g_free. Otherwise %NULL and @error will be set.
88  **/
89 gchar*
90 g_convert (const gchar *str,
91            gint         len,
92            const gchar *to_codeset,
93            const gchar *from_codeset,
94            gint        *bytes_read,
95            gint        *bytes_written,
96            GError     **error)
97 {
98   gchar *dest;
99   gchar *outp;
100   const gchar *p;
101   size_t inbytes_remaining;
102   size_t outbytes_remaining;
103   size_t err;
104   iconv_t cd;
105   size_t outbuf_size;
106   gboolean have_error = FALSE;
107   
108   g_return_val_if_fail (str != NULL, NULL);
109   g_return_val_if_fail (to_codeset != NULL, NULL);
110   g_return_val_if_fail (from_codeset != NULL, NULL);
111      
112   cd = open_converter (to_codeset, from_codeset, error);
113
114   if (cd == (iconv_t) -1)
115     {
116       if (bytes_read)
117         *bytes_read = 0;
118       
119       if (bytes_written)
120         *bytes_written = 0;
121       
122       return NULL;
123     }
124
125   if (len < 0)
126     len = strlen (str);
127
128   p = str;
129   inbytes_remaining = len;
130   outbuf_size = len + 1; /* + 1 for nul in case len == 1 */
131   outbytes_remaining = outbuf_size - 1; /* -1 for nul */
132   outp = dest = g_malloc (outbuf_size);
133
134  again:
135   
136   err = iconv (cd, &p, &inbytes_remaining, &outp, &outbytes_remaining);
137
138   if (err == (size_t) -1)
139     {
140       switch (errno)
141         {
142         case EINVAL:
143           /* Incomplete text, do not report an error */
144           break;
145         case E2BIG:
146           {
147             size_t used = outp - dest;
148             outbuf_size *= 2;
149             dest = g_realloc (dest, outbuf_size);
150
151             outp = dest + used;
152             outbytes_remaining = outbuf_size - used - 1; /* -1 for nul */
153
154             goto again;
155           }
156         case EILSEQ:
157           g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE,
158                        _("Invalid byte sequence in conversion input"));
159           have_error = TRUE;
160           break;
161         default:
162           g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_FAILED,
163                        _("Error during conversion: %s"),
164                        strerror (errno));
165           have_error = TRUE;
166           break;
167         }
168     }
169
170   *outp = '\0';
171   
172   iconv_close (cd);
173
174   if (bytes_read)
175     *bytes_read = p - str;
176
177   if (bytes_written)
178     *bytes_written = outp - dest;       /* Doesn't include '\0' */
179
180   if (have_error)
181     {
182       g_free (dest);
183       return NULL;
184     }
185   else
186     return dest;
187 }
188
189 /**
190  * g_convert_with_fallback:
191  * @str:          the string to convert
192  * @len:          the length of the string
193  * @to_codeset:   name of character set into which to convert @str
194  * @from_codeset: character set of @str.
195  * @fallback:     UTF-8 string to use in place of character not
196  *                present in the target encoding. (This must be
197  *                in the target encoding), if %NULL, characters
198  *                not in the target encoding will be represented
199  *                as Unicode escapes \x{XXXX} or \x{XXXXXX}.
200  * @bytes_read:   location to store the number of bytes in the
201  *                input string that were successfully converted, or %NULL.
202  *                Even if the conversion was succesful, this may be 
203  *                less than len if there were partial characters
204  *                at the end of the input. If the error
205  *                G_CONVERT_ERROR_ILLEGAL_SEQUENCE occurs, the value
206  *                stored will the byte fofset after the last valid
207  *                input sequence.
208  * @bytes_written: the stored in the output buffer (not including the
209  *                 terminating nul.
210  * @error:        location to store the error occuring, or %NULL to ignore
211  *                errors. Any of the errors in #GConvertError may occur.
212  *
213  * Convert a string from one character set to another, possibly
214  * including fallback sequences for characters not representable
215  * in the output. Note that it is not guaranteed that the specification
216  * for the fallback sequences in @fallback will be honored. Some
217  * systems may do a approximate conversion from @from_codeset
218  * to @to_codeset in their iconv() functions, in which case GLib
219  * will simply return that approximate conversion.
220  *
221  * Return value: If the conversion was successful, a newly allocated
222  *               NUL-terminated string, which must be freed with
223  *               g_free. Otherwise %NULL and @error will be set.
224  **/
225 gchar*
226 g_convert_with_fallback (const gchar *str,
227                          gint         len,
228                          const gchar *to_codeset,
229                          const gchar *from_codeset,
230                          gchar       *fallback,
231                          gint        *bytes_read,
232                          gint        *bytes_written,
233                          GError     **error)
234 {
235   gchar *utf8;
236   gchar *dest;
237   gchar *outp;
238   const gchar *insert_str = NULL;
239   const gchar *p;
240   int inbytes_remaining;
241   const gchar *save_p = NULL;
242   size_t save_inbytes = 0;
243   size_t outbytes_remaining;
244   size_t err;
245   iconv_t cd;
246   size_t outbuf_size;
247   gboolean have_error = FALSE;
248   gboolean done = FALSE;
249
250   GError *local_error = NULL;
251   
252   g_return_val_if_fail (str != NULL, NULL);
253   g_return_val_if_fail (to_codeset != NULL, NULL);
254   g_return_val_if_fail (from_codeset != NULL, NULL);
255      
256   if (len < 0)
257     len = strlen (str);
258   
259   /* Try an exact conversion; we only proceed if this fails
260    * due to an illegal sequence in the input string.
261    */
262   dest = g_convert (str, len, to_codeset, from_codeset, 
263                     bytes_read, bytes_written, &local_error);
264   if (!local_error)
265     return dest;
266
267   if (!g_error_matches (local_error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE))
268     {
269       g_propagate_error (error, local_error);
270       return NULL;
271     }
272   else
273     g_error_free (local_error);
274
275   /* No go; to proceed, we need a converter from "UTF-8" to
276    * to_codeset, and the string as UTF-8.
277    */
278   cd = open_converter (to_codeset, "UTF-8", error);
279   if (cd == (iconv_t) -1)
280     {
281       if (bytes_read)
282         *bytes_read = 0;
283       
284       if (bytes_written)
285         *bytes_written = 0;
286       
287       return NULL;
288     }
289
290   utf8 = g_convert (str, len, "UTF-8", from_codeset, 
291                     bytes_read, &inbytes_remaining, error);
292   if (!utf8)
293     return NULL;
294
295   /* Now the heart of the code. We loop through the UTF-8 string, and
296    * whenever we hit an offending character, we form fallback, convert
297    * the fallback to the target codeset, and then go back to
298    * converting the original string after finishing with the fallback.
299    *
300    * The variables save_p and save_inbytes store the input state
301    * for the original string while we are converting the fallback
302    */
303   p = utf8;
304   outbuf_size = len + 1; /* + 1 for nul in case len == 1 */
305   outbytes_remaining = outbuf_size - 1; /* -1 for nul */
306   outp = dest = g_malloc (outbuf_size);
307
308   while (!done && !have_error)
309     {
310       size_t inbytes_tmp = inbytes_remaining;
311       err = iconv (cd, &p, &inbytes_tmp, &outp, &outbytes_remaining);
312       inbytes_remaining = inbytes_tmp;
313
314       if (err == (size_t) -1)
315         {
316           switch (errno)
317             {
318             case EINVAL:
319               g_assert_not_reached();
320               break;
321             case E2BIG:
322               {
323                 size_t used = outp - dest;
324                 outbuf_size *= 2;
325                 dest = g_realloc (dest, outbuf_size);
326                 
327                 outp = dest + used;
328                 outbytes_remaining = outbuf_size - used - 1; /* -1 for nul */
329                 
330                 break;
331               }
332             case EILSEQ:
333               if (save_p)
334                 {
335                   /* Error converting fallback string - fatal
336                    */
337                   g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE,
338                                _("Cannot convert fallback '%s' to codeset '%s'"),
339                                insert_str, to_codeset);
340                   have_error = TRUE;
341                   break;
342                 }
343               else
344                 {
345                   if (!fallback)
346                     { 
347                       gunichar ch = g_utf8_get_char (p);
348                       insert_str = g_strdup_printf ("\\x{%0*X}",
349                                                     (ch < 0x10000) ? 4 : 6,
350                                                     ch);
351                     }
352                   else
353                     insert_str = fallback;
354                   
355                   save_p = g_utf8_next_char (p);
356                   save_inbytes = inbytes_remaining - (save_p - p);
357                   p = insert_str;
358                   inbytes_remaining = strlen (p);
359                 }
360               break;
361             default:
362               g_set_error (error, G_CONVERT_ERROR, G_CONVERT_ERROR_FAILED,
363                            _("Error during conversion: %s"),
364                            strerror (errno));
365               have_error = TRUE;
366               break;
367             }
368         }
369       else
370         {
371           if (save_p)
372             {
373               if (!fallback)
374                 g_free ((gchar *)insert_str);
375               p = save_p;
376               inbytes_remaining = save_inbytes;
377               save_p = NULL;
378             }
379           else
380             done = TRUE;
381         }
382     }
383
384   /* Cleanup
385    */
386   *outp = '\0';
387   
388   iconv_close (cd);
389
390   if (bytes_written)
391     *bytes_written = outp - str;        /* Doesn't include '\0' */
392
393   g_free (utf8);
394
395   if (have_error)
396     {
397       if (save_p && !fallback)
398         g_free ((gchar *)insert_str);
399       g_free (dest);
400       return NULL;
401     }
402   else
403     return dest;
404 }