Made conversion interface more general.
[gedcom-parse.git] / utf8 / utf8-convert.c
1 /* Encoding utility from UTF-8 to locale 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_OUTSIZE 256
20 #define DEFAULT_UNKNOWN "?"
21
22 struct conv_buffer* create_conv_buffer(int size)
23 {
24   struct conv_buffer* buf = NULL;
25
26   buf = (struct conv_buffer*) malloc(sizeof(struct conv_buffer));
27   if (buf) {
28     buf->size   = size;
29     buf->buffer = (char*)malloc(size);
30     if (!buf->buffer)
31       buf->size = 0;
32   }
33
34   return buf;
35 }
36
37 void free_conv_buffer(struct conv_buffer* buf)
38 {
39   if (buf) {
40     free(buf->buffer);
41     free(buf);
42   }
43 }
44
45 char* grow_conv_buffer(struct conv_buffer* buf, char* curr_pos)
46 {
47   size_t outlen, new_size;
48   char*  new_buffer;
49   outlen     = curr_pos - buf->buffer;
50   new_size   = buf->size * 2;
51   new_buffer = realloc(buf->buffer, new_size);
52   if (new_buffer) {
53     buf->buffer = new_buffer;
54     buf->size   = new_size;
55     curr_pos    = buf->buffer + outlen;
56     memset(curr_pos, 0, buf->size - (curr_pos - buf->buffer));
57     return curr_pos;
58   }
59   else
60     return NULL;
61 }
62
63 convert_t initialize_utf8_conversion(const char* charset)
64 {
65   struct convert *conv = NULL;
66   int cleanup = 0;
67
68   conv = (struct convert *)malloc(sizeof(struct convert));
69   if (conv) {
70     /* Unless reset to 0 at the end, this will force cleanup */
71     cleanup = 1;
72     /* First initialize to default values */
73     conv->from_utf8  = (iconv_t)-1;
74     conv->to_utf8    = (iconv_t)-1;
75     conv->outbuf     = NULL;
76     conv->unknown    = NULL;
77
78     /* Now initialize everything to what it should be */
79     conv->from_utf8 = iconv_open(charset, "UTF-8");
80     if (conv->from_utf8 != (iconv_t)-1) {
81       conv->to_utf8 = iconv_open("UTF-8", charset);
82       if (conv->to_utf8 != (iconv_t)-1) {
83         conv->outbuf = create_conv_buffer(INITIAL_OUTSIZE);
84         if (conv->outbuf) {
85           conv->unknown = strdup(DEFAULT_UNKNOWN);
86           if (conv->unknown)
87             cleanup = 0;    /* All successful */
88         }
89       }
90     }
91   }
92
93   if (cleanup) {
94     cleanup_utf8_conversion(conv);
95     conv = NULL;
96   }
97   
98   return conv;
99 }
100
101 int conversion_set_unknown(convert_t conv, const char* unknown)
102 {
103   int result = 1;
104   
105   if (conv && unknown) {
106     char* unknown_copy = strdup(unknown);
107     if (unknown_copy) {
108       if (conv->unknown) free(conv->unknown);
109       conv->unknown = unknown_copy;
110     }
111     else
112       result = 0;
113   }
114
115   return result;
116 }
117
118 void cleanup_utf8_conversion(convert_t conv)
119 {
120   if (conv) {
121     if (conv->from_utf8 != (iconv_t)-1)
122       iconv_close(conv->from_utf8);
123     if (conv->to_utf8 != (iconv_t)-1)
124       iconv_close(conv->to_utf8);
125     if (conv->outbuf)
126       free_conv_buffer(conv->outbuf);
127     if (conv->unknown)
128       free(conv->unknown);
129     free(conv);
130   }
131 }
132
133 char* convert_from_utf8(convert_t conv, const char* input, int* conv_fails)
134 {
135   size_t insize = strlen(input);
136   size_t outsize;
137   ICONV_CONST char* inptr  = (ICONV_CONST char*) input;
138   char   *outptr;
139   size_t nconv;
140   struct conv_buffer* outbuf;
141
142   if (!conv) {
143     if (conv_fails != NULL) *conv_fails = insize;
144     return NULL;
145   }
146   /* make sure we start from an empty state */
147   iconv(conv->from_utf8, NULL, NULL, NULL, NULL);
148   if (conv_fails != NULL) *conv_fails = 0;
149   /* set up output buffer (empty it) */
150   outbuf  = conv->outbuf;
151   outptr  = outbuf->buffer;
152   outsize = outbuf->size;
153   memset(outbuf->buffer, 0, outbuf->size);
154   nconv = iconv(conv->from_utf8, &inptr, &insize, &outptr, &outsize);
155   while (nconv == (size_t)-1) {
156     if (errno == E2BIG) {
157       /* grow the output buffer */
158       outptr  = grow_conv_buffer(outbuf, outptr);
159       if (outptr)
160         outsize = outbuf->size - (outptr - outbuf->buffer);
161       else {
162         errno = ENOMEM;
163         return NULL;
164       }
165     }
166     else if (errno == EILSEQ) {
167       /* skip over character */
168       const char* unkn_ptr = conv->unknown;
169       if (conv_fails != NULL) (*conv_fails)++;
170       if ((*inptr & 0x80) == 0) {
171         /* an ASCII character, just skip one (this case is very improbable) */
172         inptr++; insize--;
173       }
174       else {
175         /* a general UTF-8 character, skip all 0x10xxxxxx bytes */
176         inptr++; insize--;
177         while ((*inptr & 0xC0) == 0x80) {
178           inptr++; insize--;
179         }
180       }
181       /* append the "unknown" string to the output */
182       while (*unkn_ptr) { *outptr++ = *unkn_ptr++; outsize--; }
183     }
184     else {
185       /* EINVAL should not happen, since we convert entire strings */
186       /* EBADF is an error which should be captured by the first if above */
187       if (conv_fails != NULL) *conv_fails += insize;
188       return NULL;
189     }
190     nconv = iconv(conv->from_utf8, &inptr, &insize, &outptr, &outsize);
191   }
192   return outbuf->buffer;
193 }
194
195 char* convert_to_utf8(convert_t conv, const char* input)
196 {
197   size_t insize  = strlen(input);
198   size_t outsize;
199   ICONV_CONST char *inptr  = (ICONV_CONST char*) input;
200   char   *outptr;
201   size_t nconv;
202   struct conv_buffer* outbuf;
203
204   if (!conv)
205     return NULL;
206   /* make sure we start from an empty state */
207   iconv(conv->to_utf8, NULL, NULL, NULL, NULL);
208   /* set up output buffer (empty it) */
209   outbuf  = conv->outbuf;
210   outptr  = outbuf->buffer;
211   outsize = outbuf->size;
212   memset(outbuf->buffer, 0, outbuf->size);
213   nconv = iconv(conv->to_utf8, &inptr, &insize, &outptr, &outsize);
214   while (nconv == (size_t)-1) {
215     if (errno == E2BIG) {
216       /* grow the output buffer */
217       outptr  = grow_conv_buffer(outbuf, outptr);
218       if (outptr)
219         outsize = outbuf->size - (outptr - outbuf->buffer);
220       else {
221         errno = ENOMEM;
222         return NULL;
223       }
224     }
225     else {
226       /* EILSEQ happens when the input doesn't match the source encoding,
227          return NULL in this case */
228       /* EINVAL should not happen, since we convert entire strings */
229       /* EBADF is an error which should be captured by the first if above */
230       return NULL;
231     }
232     nconv = iconv(conv->to_utf8, &inptr, &insize, &outptr, &outsize);
233   }
234   return outbuf->buffer;  
235 }