c753e3d509299d53e312e426a1be298544d0578a
[gnulib.git] / lib / unicodeio.c
1 /* Unicode character output to streams with locale dependent encoding.
2
3    Copyright (C) 2000 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 #ifdef HAVE_CONFIG_H
23 # include <config.h>
24 #endif
25
26 #if HAVE_STDDEF_H
27 # include <stddef.h>
28 #endif
29
30 #include <stdio.h>
31 #if HAVE_STRING_H
32 # include <string.h>
33 #else
34 # include <strings.h>
35 #endif
36
37 #include <errno.h>
38 #ifndef errno
39 extern int errno;
40 #endif
41
42 #if HAVE_LIMITS_H
43 # include <limits.h>
44 #endif
45
46 /* MB_LEN_MAX is incorrectly defined to be 1 in at least one GCC
47    installation; work around this configuration error.  */
48 #if MB_LEN_MAX < 6
49 # undef MB_LEN_MAX
50 # define MB_LEN_MAX 6
51 #endif
52
53 #if HAVE_ICONV
54 # include <iconv.h>
55 #endif
56
57 #include <error.h>
58
59 #if ENABLE_NLS
60 # include <libintl.h>
61 # define _(Text) gettext (Text)
62 #else
63 # define _(Text) Text
64 #endif
65
66 #include "unicodeio.h"
67
68 #if __STDC_ISO_10646__ && HAVE_WCTOMB
69
70 /* Values of type wchar_t are Unicode code points.  */
71
72 /* Place into BUF the locale-dependent representation of the character
73    CODE.  Return the size of the result.  If there is a conversion
74    error, return -1, setting errno appropriately.  Assumes that the
75    locale doesn't change between two calls.  */
76 static size_t
77 convert_unicode_char (char buf[MB_LEN_MAX], unsigned int code)
78 {
79   wchar_t wc = code;
80   errno = 0;
81   /* Test for truncation before invoking wctomb.  */
82   return wc == code ? wctomb (buf, wc) : -1;
83 }
84
85 #else
86
87 /* When we pass a Unicode character to iconv(), we must pass it in a
88    suitable encoding. The standardized Unicode encodings are
89    UTF-8, UCS-2, UCS-4, UTF-16, UTF-16BE, UTF-16LE, UTF-7.
90    UCS-2 supports only characters up to \U0000FFFF.
91    UTF-16 and variants support only characters up to \U0010FFFF.
92    UTF-7 is way too complex and not supported by glibc-2.1.
93    UCS-4 specification leaves doubts about endianness and byte order
94    mark. glibc currently interprets it as big endian without byte order
95    mark, but this is not backed by an RFC.
96    So we use UTF-8. It supports characters up to \U7FFFFFFF and is
97    unambiguously defined.  */
98
99 /* Stores the UTF-8 representation of the Unicode character wc in r[0..5].
100    Returns the number of bytes stored, or -1 if wc is out of range.  */
101 static int
102 utf8_wctomb (unsigned char *r, unsigned int wc)
103 {
104   int count;
105
106   if (wc < 0x80)
107     count = 1;
108   else if (wc < 0x800)
109     count = 2;
110   else if (wc < 0x10000)
111     count = 3;
112   else if (wc < 0x200000)
113     count = 4;
114   else if (wc < 0x4000000)
115     count = 5;
116   else if (wc <= 0x7fffffff)
117     count = 6;
118   else
119     return -1;
120
121   switch (count)
122     {
123       /* Note: code falls through cases! */
124       case 6: r[5] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x4000000;
125       case 5: r[4] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x200000;
126       case 4: r[3] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x10000;
127       case 3: r[2] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0x800;
128       case 2: r[1] = 0x80 | (wc & 0x3f); wc = wc >> 6; wc |= 0xc0;
129       case 1: r[0] = wc;
130     }
131
132   return count;
133 }
134
135 /* Luckily, the encoding's name is platform independent.  */
136 # define UTF8_NAME "UTF-8"
137
138 /* Place into BUF the locale-dependent representation of the character
139    CODE.  Return the size of the result.  If there is a conversion
140    error, return -1, setting errno appropriately.  Assumes that the
141    locale doesn't change between two calls.  */
142 static size_t
143 convert_unicode_char (char buf[MB_LEN_MAX], unsigned int code)
144 {
145   static int initialized;
146   static int is_utf8;
147 # if HAVE_ICONV
148   static iconv_t utf8_to_local;
149 # endif
150
151   if (!initialized)
152     {
153       extern const char *locale_charset PARAMS ((void));
154       const char *charset = locale_charset ();
155
156       is_utf8 = (charset != NULL && !strcmp (charset, UTF8_NAME));
157 # if HAVE_ICONV
158       if (!is_utf8)
159         {
160           utf8_to_local = (charset != NULL
161                            ? iconv_open (charset, UTF8_NAME)
162                            : (iconv_t) -1);
163           if (utf8_to_local == (iconv_t) -1)
164             {
165               /* For an unknown encoding, assume ASCII.  */
166               utf8_to_local = iconv_open ("ASCII", UTF8_NAME);
167               if (utf8_to_local == (iconv_t) -1)
168                 {
169                   errno = ENOTSUP;
170                   return -1;
171                 }
172             }
173         }
174 # endif
175       initialized = 1;
176     }
177
178   /* Convert the character to UTF-8.  */
179   if (is_utf8)
180     return utf8_wctomb ((unsigned char *) buf, code);
181   else
182     {
183 # if HAVE_ICONV
184       char inbuf[6];
185       const char *inptr = inbuf;
186       size_t inbytesleft = utf8_wctomb ((unsigned char *) inbuf, code);
187       char *outptr = buf;
188       size_t outbytesleft = MB_LEN_MAX;
189       size_t res;
190
191       if (inbytesleft == (size_t) -1)
192         return -1;
193
194       /* Convert the character from UTF-8 to the locale's charset.  */
195       res = iconv (utf8_to_local, &inptr, &inbytesleft, &outptr, &outbytesleft);
196       if (inbytesleft > 0 || res == (size_t) -1
197           /* Irix iconv() inserts a NUL byte if it cannot convert. */
198 #  if !defined _LIBICONV_VERSION && (defined sgi || defined __sgi)
199           || (res > 0 && code != 0 && outptr - outbuf == 1 && *outbuf == '\0')
200 #  endif
201           )
202         return -1;
203
204       /* Avoid glibc-2.1 bug and Solaris 2.7 bug.  */
205 #  if defined _LIBICONV_VERSION \
206     || !((__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) || defined __sun)
207
208       /* Get back to the initial shift state.  */
209       return iconv (utf8_to_local, NULL, NULL, &outptr, &outbytesleft);
210 #  endif
211
212       return outptr - buf;
213 # else
214       errno = ENOTSUP;
215       return -1;
216 # endif
217     }
218 }
219
220 #endif
221
222 /* Output the Unicode character CODE to the output stream STREAM.  */
223 void
224 print_unicode_char (FILE *stream, unsigned int code)
225 {
226   char buf[MB_LEN_MAX];
227   size_t s = convert_unicode_char (buf, code);
228
229   if (s == (size_t) -1)
230     error (1, errno, _("cannot convert U+%04X to local character set"), code);
231   else
232     fwrite (buf, 1, s, stream);
233 }