Converted to separate package.
[gedcom-parse.git] / utf8 / utf8-convert.c
1 /* Encoding utility from UTF-8 to another charset and vice versa
2    Copyright (C) 2001, 2002 Peter Verthez
3
4    The UTF8 tools library is free software; you can redistribute it
5    and/or modify it under the terms of the GNU Lesser General Public
6    License as published by the Free Software Foundation; either
7    version 2.1 of the License, or (at your option) any later version.
8
9    The Gedcom parser library is distributed in the hope that it will be
10    useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12    Lesser General Public License for more details.
13
14    You should have received a copy of the GNU Lesser General Public
15    License along with the Gedcom parser library; if not, write to the
16    Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
17    02111-1307 USA.
18 */
19
20 /* $Id$ */
21 /* $Name$ */
22
23 #include "utf8tools.h"
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <iconv.h>
28 #include "config.h"
29
30 #define INITIAL_BUFSIZE 256
31 #define DEFAULT_UNKNOWN "?"
32
33 #define INTERNAL_BUFFER 0
34 #define EXTERNAL_BUFFER 1
35
36 struct conv_buffer {
37   char*   buffer;
38   size_t  size;
39   int     type;  /* For internal use */
40 };
41
42 struct convert {
43   iconv_t from_utf8;
44   iconv_t to_utf8;
45   struct conv_buffer* inbuf;
46   size_t  insize;
47   struct conv_buffer* outbuf;
48   char*   unknown;
49 };
50
51 void reset_conv_buffer(conv_buffer_t buf)
52 {
53   memset(buf->buffer, 0, buf->size);
54 }
55
56 conv_buffer_t create_conv_buffer(int size)
57 {
58   struct conv_buffer* buf = NULL;
59
60   if (size == 0) size = INITIAL_BUFSIZE;
61   
62   buf = (struct conv_buffer*) malloc(sizeof(struct conv_buffer));
63   if (buf) {
64     buf->size   = size;
65     buf->buffer = (char*)malloc(size);
66     buf->type   = EXTERNAL_BUFFER;
67     if (buf->buffer)
68       reset_conv_buffer(buf);
69     else
70       buf->size = 0;
71   }
72
73   return buf;
74 }
75
76 void free_conv_buffer(conv_buffer_t buf)
77 {
78   if (buf) {
79     free(buf->buffer);
80     free(buf);
81   }
82 }
83
84 char* grow_conv_buffer(conv_buffer_t buf, char* curr_pos)
85 {
86   size_t outlen, new_size;
87   char*  new_buffer;
88   outlen     = curr_pos - buf->buffer;
89   new_size   = buf->size * 2;
90   new_buffer = realloc(buf->buffer, new_size);
91   if (new_buffer) {
92     buf->buffer = new_buffer;
93     buf->size   = new_size;
94     curr_pos    = buf->buffer + outlen;
95     memset(curr_pos, 0, buf->size - (curr_pos - buf->buffer));
96     return curr_pos;
97   }
98   else
99     return NULL;
100 }
101
102 convert_t initialize_utf8_conversion(const char* charset, int external_outbuf)
103 {
104   struct convert *conv = NULL;
105   int save_errno = 0;
106   int cleanup = 0;
107
108   conv = (struct convert *)malloc(sizeof(struct convert));
109   if (conv) {
110     /* Unless reset to 0 at the end, this will force cleanup */
111     cleanup = 1;
112     /* First initialize to default values */
113     conv->from_utf8  = (iconv_t)-1;
114     conv->to_utf8    = (iconv_t)-1;
115     conv->inbuf      = NULL;
116     conv->insize     = 0;
117     conv->outbuf     = NULL;
118     conv->unknown    = NULL;
119
120     /* Now initialize everything to what it should be */
121     conv->from_utf8 = iconv_open(charset, "UTF-8");
122     if (conv->from_utf8 != (iconv_t)-1) {
123       conv->to_utf8 = iconv_open("UTF-8", charset);
124       if (conv->to_utf8 != (iconv_t)-1) {
125         conv->unknown = strdup(DEFAULT_UNKNOWN);
126         if (conv->unknown) {
127           conv->inbuf = create_conv_buffer(INITIAL_BUFSIZE);
128           conv->inbuf->type = INTERNAL_BUFFER;
129           if (conv->inbuf) {
130             if (external_outbuf)
131               cleanup = 0;
132             else {
133               conv->outbuf = create_conv_buffer(INITIAL_BUFSIZE);
134               conv->outbuf->type = INTERNAL_BUFFER;
135               if (conv->outbuf)
136                 cleanup = 0;    /* All successful */
137             }
138           }
139         }
140       }
141     }
142   }
143
144   if (cleanup) {
145     save_errno = errno;
146     cleanup_utf8_conversion(conv);
147     errno = save_errno;
148     conv = NULL;
149   }
150   
151   return conv;
152 }
153
154 int conversion_set_unknown(convert_t conv, const char* unknown)
155 {
156   int result = 1;
157   
158   if (conv && unknown) {
159     char* unknown_copy = strdup(unknown);
160     if (unknown_copy) {
161       if (conv->unknown) free(conv->unknown);
162       conv->unknown = unknown_copy;
163     }
164     else
165       result = 0;
166   }
167
168   return result;
169 }
170
171 int conversion_set_output_buffer(convert_t conv, conv_buffer_t buf)
172 {
173   if (!conv)
174     return 0;
175   else if ((!conv->outbuf || conv->outbuf->type == EXTERNAL_BUFFER)
176            && buf && buf->type == EXTERNAL_BUFFER) {
177     conv->outbuf = buf;
178     return 1;
179   }
180   else
181     return 0;
182 }
183
184 void cleanup_utf8_conversion(convert_t conv)
185 {
186   if (conv) {
187     if (conv->from_utf8 != (iconv_t)-1)
188       iconv_close(conv->from_utf8);
189     if (conv->to_utf8 != (iconv_t)-1)
190       iconv_close(conv->to_utf8);
191     if (conv->inbuf && conv->inbuf->type == INTERNAL_BUFFER)
192       free_conv_buffer(conv->inbuf);
193     if (conv->outbuf && conv->outbuf->type == INTERNAL_BUFFER)
194       free_conv_buffer(conv->outbuf);
195     if (conv->unknown)
196       free(conv->unknown);
197     free(conv);
198   }
199 }
200
201 char* convert_from_utf8(convert_t conv, const char* input, int* conv_fails,
202                         size_t* output_len)
203 {
204   size_t insize = strlen(input);
205   size_t outsize;
206   ICONV_CONST char* inptr  = (ICONV_CONST char*) input;
207   char   *outptr;
208   size_t nconv;
209   struct conv_buffer* outbuf;
210
211   if (!conv || !conv->outbuf) {
212     if (conv_fails != NULL) *conv_fails = insize;
213     return NULL;
214   }
215   /* make sure we start from an empty state */
216   iconv(conv->from_utf8, NULL, NULL, NULL, NULL);
217   if (conv_fails != NULL) *conv_fails = 0;
218   /* set up output buffer (empty it) */
219   outbuf  = conv->outbuf;
220   outptr  = outbuf->buffer;
221   outsize = outbuf->size;
222   reset_conv_buffer(conv->outbuf);
223   nconv = iconv(conv->from_utf8, &inptr, &insize, &outptr, &outsize);
224   while (nconv == (size_t)-1) {
225     if (errno == E2BIG) {
226       /* grow the output buffer */
227       outptr  = grow_conv_buffer(outbuf, outptr);
228       if (outptr)
229         outsize = outbuf->size - (outptr - outbuf->buffer);
230       else {
231         errno = ENOMEM;
232         return NULL;
233       }
234     }
235     else if (errno == EILSEQ) {
236       /* skip over character */
237       const char* unkn_ptr = conv->unknown;
238       if (conv_fails != NULL) (*conv_fails)++;
239       if ((*inptr & 0x80) == 0) {
240         /* an ASCII character, just skip one (this case is very improbable) */
241         inptr++; insize--;
242       }
243       else {
244         /* a general UTF-8 character, skip all 0x10xxxxxx bytes */
245         inptr++; insize--;
246         while ((*inptr & 0xC0) == 0x80) {
247           inptr++; insize--;
248         }
249       }
250       /* append the "unknown" string to the output */
251       while (*unkn_ptr) { *outptr++ = *unkn_ptr++; outsize--; }
252     }
253     else {
254       /* EINVAL should not happen, since we convert entire strings */
255       /* EBADF is an error which should be captured by the first if above */
256       if (conv_fails != NULL) *conv_fails += insize;
257       return NULL;
258     }
259     nconv = iconv(conv->from_utf8, &inptr, &insize, &outptr, &outsize);
260   }
261   if (output_len) *output_len = outptr - outbuf->buffer;
262   return outbuf->buffer;
263 }
264
265 char* convert_to_utf8(convert_t conv, const char* input, size_t input_len)
266 {
267   size_t outsize;
268   ICONV_CONST char *inptr  = (ICONV_CONST char*) input;
269   char   *outptr;
270   size_t nconv;
271   struct conv_buffer* outbuf;
272
273   if (!conv || !conv->outbuf)
274     return NULL;
275   /* make sure we start from an empty state */
276   iconv(conv->to_utf8, NULL, NULL, NULL, NULL);
277   /* set up output buffer (empty it) */
278   outbuf  = conv->outbuf;
279   outptr  = outbuf->buffer;
280   outsize = outbuf->size;
281   reset_conv_buffer(conv->outbuf);
282   nconv = iconv(conv->to_utf8, &inptr, &input_len, &outptr, &outsize);
283   while (nconv == (size_t)-1) {
284     if (errno == E2BIG) {
285       /* grow the output buffer */
286       outptr  = grow_conv_buffer(outbuf, outptr);
287       if (outptr)
288         outsize = outbuf->size - (outptr - outbuf->buffer);
289       else {
290         errno = ENOMEM;
291         return NULL;
292       }
293     }
294     else {
295       /* EILSEQ happens when the input doesn't match the source encoding,
296          return NULL in this case */
297       /* EINVAL should not happen, since we convert entire strings */
298       /* EBADF is an error which should be captured by the first if above */
299       return NULL;
300     }
301     nconv = iconv(conv->to_utf8, &inptr, &input_len, &outptr, &outsize);
302   }
303   return outbuf->buffer;  
304 }
305
306 char* convert_to_utf8_incremental(convert_t conv,
307                                   const char* input, size_t input_len)
308 {
309   size_t res;
310   struct conv_buffer* outbuf = conv->outbuf;
311   struct conv_buffer* inbuf  = conv->inbuf;
312   size_t outsize = outbuf->size;
313   char* wrptr = outbuf->buffer;
314   ICONV_CONST char* rdptr = (ICONV_CONST char*) inbuf->buffer;
315   char* retval = outbuf->buffer;
316
317   if (!conv || !conv->outbuf)
318     return NULL;
319   
320   /* set up input buffer (concatenate to what was left previous time) */
321   /* can't use strcpy, because possible null bytes from unicode */
322   while (conv->insize + input_len > inbuf->size)
323     grow_conv_buffer(inbuf, inbuf->buffer + conv->insize);
324   memcpy(inbuf->buffer + conv->insize, input, input_len);
325   conv->insize += input_len;
326
327   /* set up output buffer (empty it) */
328   reset_conv_buffer(outbuf);
329
330   /* do the conversion */
331   res = iconv(conv->to_utf8, &rdptr, &conv->insize, &wrptr, &outsize);
332   if (res == (size_t)-1) {
333     if (errno == EILSEQ) {
334       /* restart from an empty state and return NULL */
335       retval = NULL;
336       rdptr++;
337       conv->insize--;
338     }
339     else if (errno == EINVAL) {
340       /* Do nothing, leave it to next iteration */
341     }
342     else {
343       retval = NULL;
344     }
345   }
346
347   /* then shift what is left over to the head of the input buffer */
348   memmove(inbuf->buffer, rdptr, conv->insize);
349   memset(inbuf->buffer + conv->insize, 0, inbuf->size - conv->insize);
350   return retval;
351 }