Assume ANSI C.
[gnulib.git] / lib / unicodeio.c
1 /* Unicode character output to streams with locale dependent encoding.
2
3    Copyright (C) 2000-2003 Free Software Foundation, Inc.
4
5    This program is free software; you can redistribute it and/or modify it
6    under the terms of the GNU Library General Public License as published
7    by the Free Software Foundation; either version 2, or (at your option)
8    any later version.
9
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14
15    You should have received a copy of the GNU Library General Public
16    License along with this program; if not, write to the Free Software
17    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
18    USA.  */
19
20 /* Written by Bruno Haible <haible@clisp.cons.org>.  */
21
22 /* Note: This file requires the locale_charset() function.  See in
23    libiconv-1.8/libcharset/INTEGRATE for how to obtain it.  */
24
25 #ifdef HAVE_CONFIG_H
26 # include <config.h>
27 #endif
28
29 /* Specification.  */
30 #include "unicodeio.h"
31
32 #include <stdio.h>
33 #if HAVE_STRING_H
34 # include <string.h>
35 #else
36 # include <strings.h>
37 #endif
38
39 #include <errno.h>
40 #ifndef errno
41 extern int errno;
42 #endif
43
44 #if HAVE_ICONV
45 # include <iconv.h>
46 #endif
47
48 #include <error.h>
49
50 #include "gettext.h"
51 #define _(msgid) gettext (msgid)
52 #define N_(msgid) msgid
53
54 #include "localcharset.h"
55
56 /* When we pass a Unicode character to iconv(), we must pass it in a
57    suitable encoding. The standardized Unicode encodings are
58    UTF-8, UCS-2, UCS-4, UTF-16, UTF-16BE, UTF-16LE, UTF-7.
59    UCS-2 supports only characters up to \U0000FFFF.
60    UTF-16 and variants support only characters up to \U0010FFFF.
61    UTF-7 is way too complex and not supported by glibc-2.1.
62    UCS-4 specification leaves doubts about endianness and byte order
63    mark. glibc currently interprets it as big endian without byte order
64    mark, but this is not backed by an RFC.
65    So we use UTF-8. It supports characters up to \U7FFFFFFF and is
66    unambiguously defined.  */
67
68 /* Stores the UTF-8 representation of the Unicode character wc in r[0..5].
69    Returns the number of bytes stored, or -1 if wc is out of range.  */
70 static int
71 utf8_wctomb (unsigned char *r, unsigned int wc)
72 {
73   int count;
74
75   if (wc < 0x80)
76     count = 1;
77   else if (wc < 0x800)
78     count = 2;
79   else if (wc < 0x10000)
80     count = 3;
81   else if (wc < 0x200000)
82     count = 4;
83   else if (wc < 0x4000000)
84     count = 5;
85   else if (wc <= 0x7fffffff)
86     count = 6;
87   else
88     return -1;
89
90   switch (count)
91     {
92       /* Note: code falls through cases! */
93       case 6: r[5] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x4000000;
94       case 5: r[4] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x200000;
95       case 4: r[3] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x10000;
96       case 3: r[2] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x800;
97       case 2: r[1] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0xc0;
98       case 1: r[0] = wc;
99     }
100
101   return count;
102 }
103
104 /* Luckily, the encoding's name is platform independent.  */
105 #define UTF8_NAME "UTF-8"
106
107 /* Converts the Unicode character CODE to its multibyte representation
108    in the current locale and calls the SUCCESS callback on the resulting
109    byte sequence.  If an error occurs, invokes the FAILURE callback instead,
110    passing it CODE and an English error string.
111    Returns whatever the callback returned.
112    Assumes that the locale doesn't change between two calls.  */
113 long
114 unicode_to_mb (unsigned int code,
115                long (*success) (const char *buf, size_t buflen,
116                                 void *callback_arg),
117                long (*failure) (unsigned int code, const char *msg,
118                                 void *callback_arg),
119                void *callback_arg)
120 {
121   static int initialized;
122   static int is_utf8;
123 #if HAVE_ICONV
124   static iconv_t utf8_to_local;
125 #endif
126
127   char inbuf[6];
128   int count;
129
130   if (!initialized)
131     {
132       const char *charset = locale_charset ();
133
134       is_utf8 = !strcmp (charset, UTF8_NAME);
135 #if HAVE_ICONV
136       if (!is_utf8)
137         {
138           utf8_to_local = iconv_open (charset, UTF8_NAME);
139           if (utf8_to_local == (iconv_t)(-1))
140             /* For an unknown encoding, assume ASCII.  */
141             utf8_to_local = iconv_open ("ASCII", UTF8_NAME);
142         }
143 #endif
144       initialized = 1;
145     }
146
147   /* Test whether the utf8_to_local converter is available at all.  */
148   if (!is_utf8)
149     {
150 #if HAVE_ICONV
151       if (utf8_to_local == (iconv_t)(-1))
152         return failure (code, N_("iconv function not usable"), callback_arg);
153 #else
154       return failure (code, N_("iconv function not available"), callback_arg);
155 #endif
156     }
157
158   /* Convert the character to UTF-8.  */
159   count = utf8_wctomb ((unsigned char *) inbuf, code);
160   if (count < 0)
161     return failure (code, N_("character out of range"), callback_arg);
162
163 #if HAVE_ICONV
164   if (!is_utf8)
165     {
166       char outbuf[25];
167       const char *inptr;
168       size_t inbytesleft;
169       char *outptr;
170       size_t outbytesleft;
171       size_t res;
172
173       inptr = inbuf;
174       inbytesleft = count;
175       outptr = outbuf;
176       outbytesleft = sizeof (outbuf);
177
178       /* Convert the character from UTF-8 to the locale's charset.  */
179       res = iconv (utf8_to_local,
180                    (ICONV_CONST char **)&inptr, &inbytesleft,
181                    &outptr, &outbytesleft);
182       if (inbytesleft > 0 || res == (size_t)(-1)
183           /* Irix iconv() inserts a NUL byte if it cannot convert. */
184 # if !defined _LIBICONV_VERSION && (defined sgi || defined __sgi)
185           || (res > 0 && code != 0 && outptr - outbuf == 1 && *outbuf == '\0')
186 # endif
187          )
188         return failure (code, NULL, callback_arg);
189
190       /* Avoid glibc-2.1 bug and Solaris 2.7 bug.  */
191 # if defined _LIBICONV_VERSION \
192     || !((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) || defined __sun)
193
194       /* Get back to the initial shift state.  */
195       res = iconv (utf8_to_local, NULL, NULL, &outptr, &outbytesleft);
196       if (res == (size_t)(-1))
197         return failure (code, NULL, callback_arg);
198 # endif
199
200       return success (outbuf, outptr - outbuf, callback_arg);
201     }
202 #endif
203
204   /* At this point, is_utf8 is true, so no conversion is needed.  */
205   return success (inbuf, count, callback_arg);
206 }
207
208 /* Simple success callback that outputs the converted string.
209    The STREAM is passed as callback_arg.  */
210 long
211 fwrite_success_callback (const char *buf, size_t buflen, void *callback_arg)
212 {
213   FILE *stream = (FILE *) callback_arg;
214
215   fwrite (buf, 1, buflen, stream);
216   return 0;
217 }
218
219 /* Simple failure callback that displays an error and exits.  */
220 static long
221 exit_failure_callback (unsigned int code, const char *msg, void *callback_arg)
222 {
223   if (msg == NULL)
224     error (1, 0, _("cannot convert U+%04X to local character set"), code);
225   else
226     error (1, 0, _("cannot convert U+%04X to local character set: %s"), code,
227            gettext (msg));
228   return -1;
229 }
230
231 /* Simple failure callback that displays a fallback representation in plain
232    ASCII, using the same notation as ISO C99 strings.  */
233 static long
234 fallback_failure_callback (unsigned int code, const char *msg, void *callback_arg)
235 {
236   FILE *stream = (FILE *) callback_arg;
237
238   if (code < 0x10000)
239     fprintf (stream, "\\u%04X", code);
240   else
241     fprintf (stream, "\\U%08X", code);
242   return -1;
243 }
244
245 /* Outputs the Unicode character CODE to the output stream STREAM.
246    Upon failure, exit if exit_on_error is true, otherwise output a fallback
247    notation.  */
248 void
249 print_unicode_char (FILE *stream, unsigned int code, int exit_on_error)
250 {
251   unicode_to_mb (code, fwrite_success_callback,
252                  exit_on_error
253                  ? exit_failure_callback
254                  : fallback_failure_callback,
255                  stream);
256 }