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