Conversion changes.
[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    Permission granted to do anything with this file that you want, as long
5    as the above copyright is retained in all copies.
6    THERE IS NO WARRANTY - USE AT YOUR OWN RISK
7 */
8
9 /* $Id$ */
10 /* $Name$ */
11
12 #include "utf8.h"
13 #include <stdlib.h>
14 #include <string.h>
15 #include <errno.h>
16 #include <iconv.h>
17 #include "config.h"
18
19 #define INITIAL_BUFSIZE 256
20 #define DEFAULT_UNKNOWN "?"
21
22 #define INTERNAL_BUFFER 0
23 #define EXTERNAL_BUFFER 1
24
25 void reset_conv_buffer(struct conv_buffer* buf)
26 {
27   memset(buf->buffer, 0, buf->size);
28 }
29
30 struct conv_buffer* create_conv_buffer(int size)
31 {
32   struct conv_buffer* buf = NULL;
33
34   if (size == 0) size = INITIAL_BUFSIZE;
35   
36   buf = (struct conv_buffer*) malloc(sizeof(struct conv_buffer));
37   if (buf) {
38     buf->size   = size;
39     buf->buffer = (char*)malloc(size);
40     buf->type   = EXTERNAL_BUFFER;
41     if (buf->buffer)
42       reset_conv_buffer(buf);
43     else
44       buf->size = 0;
45   }
46
47   return buf;
48 }
49
50 void free_conv_buffer(struct conv_buffer* buf)
51 {
52   if (buf) {
53     free(buf->buffer);
54     free(buf);
55   }
56 }
57
58 char* grow_conv_buffer(struct conv_buffer* buf, char* curr_pos)
59 {
60   size_t outlen, new_size;
61   char*  new_buffer;
62   outlen     = curr_pos - buf->buffer;
63   new_size   = buf->size * 2;
64   new_buffer = realloc(buf->buffer, new_size);
65   if (new_buffer) {
66     buf->buffer = new_buffer;
67     buf->size   = new_size;
68     curr_pos    = buf->buffer + outlen;
69     memset(curr_pos, 0, buf->size - (curr_pos - buf->buffer));
70     return curr_pos;
71   }
72   else
73     return NULL;
74 }
75
76 convert_t initialize_utf8_conversion(const char* charset, int external_outbuf)
77 {
78   struct convert *conv = NULL;
79   int save_errno = 0;
80   int cleanup = 0;
81
82   conv = (struct convert *)malloc(sizeof(struct convert));
83   if (conv) {
84     /* Unless reset to 0 at the end, this will force cleanup */
85     cleanup = 1;
86     /* First initialize to default values */
87     conv->from_utf8  = (iconv_t)-1;
88     conv->to_utf8    = (iconv_t)-1;
89     conv->inbuf      = NULL;
90     conv->insize     = 0;
91     conv->outbuf     = NULL;
92     conv->unknown    = NULL;
93
94     /* Now initialize everything to what it should be */
95     conv->from_utf8 = iconv_open(charset, "UTF-8");
96     if (conv->from_utf8 != (iconv_t)-1) {
97       conv->to_utf8 = iconv_open("UTF-8", charset);
98       if (conv->to_utf8 != (iconv_t)-1) {
99         conv->unknown = strdup(DEFAULT_UNKNOWN);
100         if (conv->unknown) {
101           conv->inbuf = create_conv_buffer(INITIAL_BUFSIZE);
102           conv->inbuf->type = INTERNAL_BUFFER;
103           if (conv->inbuf) {
104             if (external_outbuf)
105               cleanup = 0;
106             else {
107               conv->outbuf = create_conv_buffer(INITIAL_BUFSIZE);
108               conv->outbuf->type = INTERNAL_BUFFER;
109               if (conv->outbuf)
110                 cleanup = 0;    /* All successful */
111             }
112           }
113         }
114       }
115     }
116   }
117
118   if (cleanup) {
119     save_errno = errno;
120     cleanup_utf8_conversion(conv);
121     errno = save_errno;
122     conv = NULL;
123   }
124   
125   return conv;
126 }
127
128 int conversion_set_unknown(convert_t conv, const char* unknown)
129 {
130   int result = 1;
131   
132   if (conv && unknown) {
133     char* unknown_copy = strdup(unknown);
134     if (unknown_copy) {
135       if (conv->unknown) free(conv->unknown);
136       conv->unknown = unknown_copy;
137     }
138     else
139       result = 0;
140   }
141
142   return result;
143 }
144
145 int conversion_set_output_buffer(convert_t conv, struct conv_buffer* buf)
146 {
147   if (!conv)
148     return 0;
149   else if ((!conv->outbuf || conv->outbuf->type == EXTERNAL_BUFFER)
150            && buf && buf->type == EXTERNAL_BUFFER) {
151     conv->outbuf = buf;
152     return 1;
153   }
154   else
155     return 0;
156 }
157
158 void cleanup_utf8_conversion(convert_t conv)
159 {
160   if (conv) {
161     if (conv->from_utf8 != (iconv_t)-1)
162       iconv_close(conv->from_utf8);
163     if (conv->to_utf8 != (iconv_t)-1)
164       iconv_close(conv->to_utf8);
165     if (conv->inbuf && conv->inbuf->type == INTERNAL_BUFFER)
166       free_conv_buffer(conv->inbuf);
167     if (conv->outbuf && conv->outbuf->type == INTERNAL_BUFFER)
168       free_conv_buffer(conv->outbuf);
169     if (conv->unknown)
170       free(conv->unknown);
171     free(conv);
172   }
173 }
174
175 char* convert_from_utf8(convert_t conv, const char* input, int* conv_fails)
176 {
177   size_t insize = strlen(input);
178   size_t outsize;
179   ICONV_CONST char* inptr  = (ICONV_CONST char*) input;
180   char   *outptr;
181   size_t nconv;
182   struct conv_buffer* outbuf;
183
184   if (!conv || !conv->outbuf) {
185     if (conv_fails != NULL) *conv_fails = insize;
186     return NULL;
187   }
188   /* make sure we start from an empty state */
189   iconv(conv->from_utf8, NULL, NULL, NULL, NULL);
190   if (conv_fails != NULL) *conv_fails = 0;
191   /* set up output buffer (empty it) */
192   outbuf  = conv->outbuf;
193   outptr  = outbuf->buffer;
194   outsize = outbuf->size;
195   reset_conv_buffer(conv->outbuf);
196   nconv = iconv(conv->from_utf8, &inptr, &insize, &outptr, &outsize);
197   while (nconv == (size_t)-1) {
198     if (errno == E2BIG) {
199       /* grow the output buffer */
200       outptr  = grow_conv_buffer(outbuf, outptr);
201       if (outptr)
202         outsize = outbuf->size - (outptr - outbuf->buffer);
203       else {
204         errno = ENOMEM;
205         return NULL;
206       }
207     }
208     else if (errno == EILSEQ) {
209       /* skip over character */
210       const char* unkn_ptr = conv->unknown;
211       if (conv_fails != NULL) (*conv_fails)++;
212       if ((*inptr & 0x80) == 0) {
213         /* an ASCII character, just skip one (this case is very improbable) */
214         inptr++; insize--;
215       }
216       else {
217         /* a general UTF-8 character, skip all 0x10xxxxxx bytes */
218         inptr++; insize--;
219         while ((*inptr & 0xC0) == 0x80) {
220           inptr++; insize--;
221         }
222       }
223       /* append the "unknown" string to the output */
224       while (*unkn_ptr) { *outptr++ = *unkn_ptr++; outsize--; }
225     }
226     else {
227       /* EINVAL should not happen, since we convert entire strings */
228       /* EBADF is an error which should be captured by the first if above */
229       if (conv_fails != NULL) *conv_fails += insize;
230       return NULL;
231     }
232     nconv = iconv(conv->from_utf8, &inptr, &insize, &outptr, &outsize);
233   }
234   return outbuf->buffer;
235 }
236
237 char* convert_to_utf8(convert_t conv, const char* input)
238 {
239   size_t insize  = strlen(input);
240   size_t outsize;
241   ICONV_CONST char *inptr  = (ICONV_CONST char*) input;
242   char   *outptr;
243   size_t nconv;
244   struct conv_buffer* outbuf;
245
246   if (!conv || !conv->outbuf)
247     return NULL;
248   /* make sure we start from an empty state */
249   iconv(conv->to_utf8, NULL, NULL, NULL, NULL);
250   /* set up output buffer (empty it) */
251   outbuf  = conv->outbuf;
252   outptr  = outbuf->buffer;
253   outsize = outbuf->size;
254   reset_conv_buffer(conv->outbuf);
255   nconv = iconv(conv->to_utf8, &inptr, &insize, &outptr, &outsize);
256   while (nconv == (size_t)-1) {
257     if (errno == E2BIG) {
258       /* grow the output buffer */
259       outptr  = grow_conv_buffer(outbuf, outptr);
260       if (outptr)
261         outsize = outbuf->size - (outptr - outbuf->buffer);
262       else {
263         errno = ENOMEM;
264         return NULL;
265       }
266     }
267     else {
268       /* EILSEQ happens when the input doesn't match the source encoding,
269          return NULL in this case */
270       /* EINVAL should not happen, since we convert entire strings */
271       /* EBADF is an error which should be captured by the first if above */
272       return NULL;
273     }
274     nconv = iconv(conv->to_utf8, &inptr, &insize, &outptr, &outsize);
275   }
276   return outbuf->buffer;  
277 }
278
279 char* convert_to_utf8_incremental(convert_t conv,
280                                   const char* input, size_t input_len)
281 {
282   size_t res;
283   struct conv_buffer* outbuf = conv->outbuf;
284   struct conv_buffer* inbuf  = conv->inbuf;
285   size_t outsize = outbuf->size;
286   char* wrptr = outbuf->buffer;
287   ICONV_CONST char* rdptr = (ICONV_CONST char*) inbuf->buffer;
288   char* retval = outbuf->buffer;
289
290   if (!conv || !conv->outbuf)
291     return NULL;
292   
293   /* set up input buffer (concatenate to what was left previous time) */
294   /* can't use strcpy, because possible null bytes from unicode */
295   while (conv->insize + input_len > inbuf->size)
296     grow_conv_buffer(inbuf, inbuf->buffer + conv->insize);
297   memcpy(inbuf->buffer + conv->insize, input, input_len);
298   conv->insize += input_len;
299
300   /* set up output buffer (empty it) */
301   reset_conv_buffer(outbuf);
302
303   /* do the conversion */
304   res = iconv(conv->to_utf8, &rdptr, &conv->insize, &wrptr, &outsize);
305   if (res == (size_t)-1) {
306     if (errno == EILSEQ) {
307       /* restart from an empty state and return NULL */
308       retval = NULL;
309       rdptr++;
310       conv->insize--;
311     }
312     else if (errno == EINVAL) {
313       /* Do nothing, leave it to next iteration */
314     }
315     else {
316       retval = NULL;
317     }
318   }
319
320   /* then shift what is left over to the head of the input buffer */
321   memmove(inbuf->buffer, rdptr, conv->insize);
322   memset(inbuf->buffer + conv->insize, 0, inbuf->size - conv->insize);
323   return retval;
324 }