d34fa08a4a58208e214dc4e1164b582252b80a9a
[platform/upstream/bash.git] / lib / sh / unicode.c
1 /* unicode.c - functions to convert unicode characters */
2
3 /* Copyright (C) 2010 Free Software Foundation, Inc.
4
5    This file is part of GNU Bash, the Bourne Again SHell.
6
7    Bash is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation, either version 3 of the License, or
10    (at your option) any later version.
11
12    Bash is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with Bash.  If not, see <http://www.gnu.org/licenses/>.
19 */
20
21 #include <config.h>
22
23 #if defined (HANDLE_MULTIBYTE)
24
25 #include <stdc.h>
26 #include <wchar.h>
27 #include <bashansi.h>
28 #ifdef HAVE_UNISTD_H
29 #include <unistd.h>
30 #endif
31 #include <limits.h>
32
33 #if HAVE_ICONV
34 #  include <iconv.h>
35 #endif
36
37 #include <xmalloc.h>
38
39 #ifndef USHORT_MAX
40 #  ifdef USHRT_MAX
41 #    define USHORT_MAX USHRT_MAX
42 #  else
43 #    define USHORT_MAX ((unsigned short) ~(unsigned short)0)
44 #  endif
45 #endif
46
47 #if !defined (STREQ)
48 #  define STREQ(a, b) ((a)[0] == (b)[0] && strcmp ((a), (b)) == 0)
49 #endif /* !STREQ */
50
51 #if defined (HAVE_LOCALE_CHARSET)
52 extern const char *locale_charset __P((void));
53 #else
54 extern char *get_locale_var __P((char *));
55 #endif
56
57 static int u32init = 0;
58 static int utf8locale = 0;
59 #if defined (HAVE_ICONV)
60 static iconv_t localconv;
61 #endif
62
63 #ifndef HAVE_LOCALE_CHARSET
64 static char *
65 stub_charset ()
66 {
67   char *locale, *s, *t;
68
69   locale = get_locale_var ("LC_CTYPE");
70   if (locale == 0 || *locale == 0)
71     return "ASCII";
72   s = strrchr (locale, '.');
73   if (s)
74     {
75       t = strchr (s, '@');
76       if (t)
77         *t = 0;
78       return ++s;
79     }
80   else if (STREQ (locale, "UTF-8"))
81     return "UTF-8";
82   else
83     return "ASCII";
84 }
85 #endif
86
87 /* u32toascii ? */
88 int
89 u32tochar (wc, s)
90      wchar_t wc;
91      char *s;
92 {
93   unsigned long x;
94   int l;
95
96   x = wc;
97   l = (x <= UCHAR_MAX) ? 1 : ((x <= USHORT_MAX) ? 2 : 4);
98
99   if (x <= UCHAR_MAX)
100     s[0] = x & 0xFF;
101   else if (x <= USHORT_MAX)     /* assume unsigned short = 16 bits */
102     {
103       s[0] = (x >> 8) & 0xFF;
104       s[1] = x & 0xFF;
105     }
106   else
107     {
108       s[0] = (x >> 24) & 0xFF;
109       s[1] = (x >> 16) & 0xFF;
110       s[2] = (x >> 8) & 0xFF;
111       s[3] = x & 0xFF;
112     }
113   s[l] = '\0';
114   return l;  
115 }
116
117 int
118 u32toutf8 (wc, s)
119      wchar_t wc;
120      char *s;
121 {
122   int l;
123
124   l = (wc < 0x0080) ? 1 : ((wc < 0x0800) ? 2 : 3);
125
126   if (wc < 0x0080)
127     s[0] = (unsigned char)wc;
128   else if (wc < 0x0800)
129     {
130       s[0] = (wc >> 6) | 0xc0;
131       s[1] = (wc & 0x3f) | 0x80;
132     }
133   else
134     {
135       s[0] = (wc >> 12) | 0xe0;
136       s[1] = ((wc >> 6) & 0x3f) | 0x80;
137       s[2] = (wc & 0x3f) | 0x80;
138     }
139   s[l] = '\0';
140   return l;
141 }
142
143 /* convert a single unicode-32 character into a multibyte string and put the
144    result in S, which must be large enough (at least MB_LEN_MAX bytes) */
145 int
146 u32cconv (c, s)
147      unsigned long c;
148      char *s;
149 {
150   wchar_t wc;
151   int n;
152 #if HAVE_ICONV
153   const char *charset;
154   char obuf[25], *optr;
155   size_t obytesleft;
156   const char *iptr;
157   size_t sn;
158 #endif
159
160   wc = c;
161
162 #if __STDC_ISO_10646__
163   if (sizeof (wchar_t) == 4)
164     {
165       n = wctomb (s, wc);
166       return n;
167     }
168 #endif
169
170 #if HAVE_NL_LANGINFO
171   codeset = nl_langinfo (CODESET);
172   if (STREQ (codeset, "UTF-8"))
173     {
174       n = u32toutf8 (wc, s);
175       return n;
176     }
177 #endif
178
179 #if HAVE_ICONV
180   /* this is mostly from coreutils-8.5/lib/unicodeio.c */
181   if (u32init == 0)
182     {
183 #  if HAVE_LOCALE_CHARSET
184       charset = locale_charset ();      /* XXX - fix later */
185 #  else
186       charset = stub_charset ();
187 #  endif
188       if (STREQ (charset, "UTF-8"))
189         utf8locale = 1;
190       else
191         {
192           localconv = iconv_open (charset, "UTF-8");
193           if (localconv == (iconv_t)-1)
194             localconv = iconv_open (charset, "ASCII");
195         }
196       u32init = 1;
197     }
198
199   if (utf8locale)
200     {
201       n = u32toutf8 (wc, s);
202       return n;
203     }
204
205   if (localconv == (iconv_t)-1)
206     {
207       n = u32tochar (wc, s);
208       return n;
209     }
210
211   n = u32toutf8 (wc, s);
212
213   optr = obuf;
214   obytesleft = sizeof (obuf);
215   iptr = s;
216   sn = n;
217
218   iconv (localconv, NULL, NULL, NULL, NULL);
219
220   if (iconv (localconv, (ICONV_CONST char **)&iptr, &sn, &optr, &obytesleft) == (size_t)-1)
221     return n;   /* You get utf-8 if iconv fails */
222
223   *optr = '\0';
224
225   /* number of chars to be copied is optr - obuf if we want to do bounds
226      checking */
227   strcpy (s, obuf);
228   return (optr - obuf);
229 #endif
230
231   n = u32tochar (wc, s);        /* fallback */
232   return n;
233 }
234
235 #endif /* HANDLE_MULTIBYTE */