Moved from test directory.
[gedcom-parse.git] / utf8 / utf8-locale.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 <stdlib.h>
13 #include <iconv.h>
14 #include <langinfo.h>
15 #include <assert.h>
16 #include <errno.h>
17 #include "utf8-locale.h"
18
19 #define INITIAL_OUTSIZE 256
20
21 static iconv_t utf8_to_locale = (iconv_t) -1;
22 static iconv_t locale_to_utf8 = (iconv_t) -1;
23 static char*   outbuffer = NULL;
24 static size_t  outbufsize = 0;
25 static const char* the_unknown = "?";
26
27 void convert_set_unknown(const char* unknown)
28 {
29   the_unknown = unknown;
30 }
31
32 void close_conversion_contexts()
33 {
34   iconv_close(utf8_to_locale);
35   iconv_close(locale_to_utf8);
36   utf8_to_locale = (iconv_t) -1;
37   locale_to_utf8 = (iconv_t) -1;
38   free(outbuffer);
39 }
40
41 int open_conversion_contexts()
42 {
43   assert(utf8_to_locale == (iconv_t) -1);
44   assert(locale_to_utf8 == (iconv_t) -1);
45   utf8_to_locale = iconv_open(nl_langinfo(CODESET), "UTF-8");
46   if (utf8_to_locale == (iconv_t) -1)
47     return -1;
48   else {
49     locale_to_utf8 = iconv_open("UTF-8", nl_langinfo(CODESET));
50     if (locale_to_utf8 == (iconv_t) -1) {
51       close_conversion_contexts();
52       return -1;
53     }
54     else {
55       outbufsize = INITIAL_OUTSIZE;
56       outbuffer = (char*)malloc(outbufsize);
57       atexit(close_conversion_contexts);
58       return 0;
59     }
60   }
61 }
62
63 char* convert_utf8_to_locale(char* input, int *conv_fails)
64 {
65   size_t insize  = strlen(input);
66   size_t outsize;
67   char   *inptr  = input;
68   char   *outptr;
69   size_t nconv;
70
71   if (utf8_to_locale == (iconv_t) -1 && (open_conversion_contexts() == -1))
72     return NULL;
73   assert(utf8_to_locale != (iconv_t) -1);
74   /* make sure we start from an empty state */
75   iconv(utf8_to_locale, NULL, NULL, NULL, NULL);
76   if (conv_fails != NULL) *conv_fails = 0;
77   /* set up output buffer (empty it) */
78   outptr  = outbuffer;
79   outsize = outbufsize;
80   memset(outbuffer, 0, outbufsize);
81   nconv = iconv(utf8_to_locale, &inptr, &insize, &outptr, &outsize);
82   while (nconv == (size_t)-1) {
83     if (errno == E2BIG) {
84       /* grow the output buffer */
85       size_t outlen;
86       outlen     = outptr - outbuffer;
87       outbufsize *= 2;
88       outbuffer  = realloc(outbuffer, outbufsize);
89       outptr     = outbuffer + outlen;
90       outsize    = outbufsize - outlen;
91       memset(outptr, 0, outsize);
92     }
93     else if (errno == EILSEQ) {
94       /* skip over character */
95       const char* unkn_ptr = the_unknown;
96       if (conv_fails != NULL) (*conv_fails)++;
97       if ((*inptr & 0x80) == 0) {
98         /* an ASCII character, just skip one (this case is very improbable) */
99         inptr++; insize--;
100       }
101       else {
102         /* a general UTF-8 character, skip all 0x10xxxxxx bytes */
103         inptr++; insize--;
104         while ((*inptr & 0xC0) == 0x80) {
105           inptr++; insize--;
106         }
107       }
108       /* append the "unknown" string to the output */
109       while (*unkn_ptr) { *outptr++ = *unkn_ptr++; outsize--; }
110     }
111     else {
112       /* EINVAL should not happen, since we convert entire strings */
113       /* EBADF is an error which should be captured by the assert above */
114       return NULL;
115     }
116     nconv = iconv(utf8_to_locale, &inptr, &insize, &outptr, &outsize);
117   }
118   return outbuffer;
119 }
120
121 char* convert_locale_to_utf8(char* input)
122 {
123   size_t insize  = strlen(input);
124   size_t outsize;
125   char   *inptr  = input;
126   char   *outptr;
127   size_t nconv;
128
129   if (locale_to_utf8 == (iconv_t) -1 && (open_conversion_contexts() == -1))
130     return NULL;
131   assert(locale_to_utf8 != (iconv_t) -1);
132   /* make sure we start from an empty state */
133   iconv(locale_to_utf8, NULL, NULL, NULL, NULL);
134   /* set up output buffer (empty it) */
135   outptr  = outbuffer;
136   outsize = outbufsize;
137   memset(outbuffer, 0, outbufsize);
138   nconv = iconv(locale_to_utf8, &inptr, &insize, &outptr, &outsize);
139   while (nconv == (size_t)-1) {
140     if (errno == E2BIG) {
141       /* grow the output buffer */
142       size_t outlen;
143       outlen     = outptr - outbuffer;
144       outbufsize *= 2;
145       outbuffer  = realloc(outbuffer, outbufsize);
146       outptr     = outbuffer + outlen;
147       outsize    = outbufsize - outlen;
148       memset(outptr, 0, outsize);
149     }
150     else {
151       /* EILSEQ should not happen, because UTF-8 can represent anything */
152       /* EINVAL should not happen, since we convert entire strings */
153       /* EBADF is an error which should be captured by the assert above */
154       return NULL;
155     }
156     nconv = iconv(locale_to_utf8, &inptr, &insize, &outptr, &outsize);
157   }
158   return outbuffer;
159 }