Use locale_charset() from iconv which will use nl_langinfo (CODESET) anyways but...
[mono.git] / eglib / src / gunicode.c
1 /*
2  * gunicode.c: Some Unicode routines 
3  *
4  * Author:
5  *   Miguel de Icaza (miguel@novell.com)
6  *
7  * (C) 2006 Novell, Inc.
8  *
9  * utf8 validation code came from:
10  *      libxml2-2.6.26 licensed under the MIT X11 license
11  *
12  * Authors credit in libxml's string.c:
13  *   William Brack <wbrack@mmm.com.hk>
14  *   daniel@veillard.com
15  *
16  * Permission is hereby granted, free of charge, to any person obtaining
17  * a copy of this software and associated documentation files (the
18  * "Software"), to deal in the Software without restriction, including
19  * without limitation the rights to use, copy, modify, merge, publish,
20  * distribute, sublicense, and/or sell copies of the Software, and to
21  * permit persons to whom the Software is furnished to do so, subject to
22  * the following conditions:
23  *
24  * The above copyright notice and this permission notice shall be
25  * included in all copies or substantial portions of the Software.
26  *
27  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
28  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
29  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
30  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
31  * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
32  * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
33  * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
34  *
35  */
36 #include <config.h>
37 #include <stdio.h>
38 #include <glib.h>
39 #include <unicode-data.h>
40 #include <errno.h>
41
42 #if defined(_MSC_VER) || defined(G_OS_WIN32)
43 /* FIXME */
44 #  define CODESET 1
45 #  include <windows.h>
46 #  ifdef _MSC_VER
47        typedef int iconv_t;
48 #  endif
49 #else
50 #    ifdef HAVE_LANGINFO_H
51 #       include <langinfo.h>
52 #    endif
53 #    ifdef HAVE_ICONV_H
54 #       include <iconv.h>
55 #    endif
56 #    ifdef HAVE_LOCALCHARSET_H
57 #       include <localcharset.h>
58 #    endif
59 #endif
60
61 static char *my_charset;
62 static gboolean is_utf8;
63
64 /*
65  * Character set conversion
66  */
67 /*
68 * Index into the table below with the first byte of a UTF-8 sequence to
69 * get the number of trailing bytes that are supposed to follow it.
70 * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is
71 * left as-is for anyone who may want to do such conversion, which was
72 * allowed in earlier algorithms.
73 */
74 const gchar g_trailingBytesForUTF8 [256] = {
75         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
76         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
77         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
78         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
79         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
80         0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
81         1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
82         2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,0,0
83 };
84
85 /*
86 * Magic values subtracted from a buffer value during UTF8 conversion.
87 * This table contains as many values as there might be trailing bytes
88 * in a UTF-8 sequence.
89 */
90 static const gulong offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL,
91 0x03C82080UL, 0xFA082080UL, 0x82082080UL };
92
93 GUnicodeType 
94 g_unichar_type (gunichar c)
95 {
96 int i;
97
98         guint16 cp = (guint16) c;
99         for (i = 0; i < unicode_category_ranges_count; i++) {
100                 if (cp < unicode_category_ranges [i].start)
101                         continue;
102                 if (unicode_category_ranges [i].end <= cp)
103                         continue;
104                 return unicode_category [i] [cp - unicode_category_ranges [i].start];
105         }
106
107         /*
108         // 3400-4DB5: OtherLetter
109         // 4E00-9FC3: OtherLetter
110         // AC00-D7A3: OtherLetter
111         // D800-DFFF: OtherSurrogate
112         // E000-F8FF: OtherPrivateUse
113         // 20000-2A6D6 OtherLetter
114         // F0000-FFFFD OtherPrivateUse
115         // 100000-10FFFD OtherPrivateUse
116         */
117         if (0x3400 <= cp && cp < 0x4DB5)
118                 return G_UNICODE_OTHER_LETTER;
119         if (0x4E00 <= cp && cp < 0x9FC3)
120                 return G_UNICODE_OTHER_LETTER;
121         if (0xAC00<= cp && cp < 0xD7A3)
122                 return G_UNICODE_OTHER_LETTER;
123         if (0xD800 <= cp && cp < 0xDFFF)
124                 return G_UNICODE_SURROGATE;
125         if (0xE000 <= cp && cp < 0xF8FF)
126                 return G_UNICODE_PRIVATE_USE;
127         /* since the argument is UTF-16, we cannot check beyond FFFF */
128
129         /* It should match any of above */
130         return 0;
131 }
132
133 gunichar
134 g_unichar_case (gunichar c, gboolean upper)
135 {
136         gint8 i, i2;
137         guint32 cp = (guint32) c, v;
138
139         for (i = 0; i < simple_case_map_ranges_count; i++) {
140                 if (cp < simple_case_map_ranges [i].start)
141                         return c;
142                 if (simple_case_map_ranges [i].end <= cp)
143                         continue;
144                 if (c < 0x10000) {
145                         const guint16 *tab = upper ? simple_upper_case_mapping_lowarea [i] : simple_lower_case_mapping_lowarea [i];
146                         v = tab [cp - simple_case_map_ranges [i].start];
147                 } else {
148                         const guint32 *tab;
149                         i2 = (gint8)(i - (upper ? simple_upper_case_mapping_lowarea_table_count : simple_lower_case_mapping_lowarea_table_count));
150                         tab = upper ? simple_upper_case_mapping_higharea [i2] : simple_lower_case_mapping_higharea [i2];
151                         v = tab [cp - simple_case_map_ranges [i].start];
152                 }
153                 return v != 0 ? (gunichar) v : c;
154         }
155         return c;
156 }
157
158 gunichar
159 g_unichar_toupper (gunichar c)
160 {
161         return g_unichar_case (c, TRUE);
162 }
163
164 gunichar
165 g_unichar_tolower (gunichar c)
166 {
167         return g_unichar_case (c, FALSE);
168 }
169
170 gunichar
171 g_unichar_totitle (gunichar c)
172 {
173         guint8 i;
174         guint32 cp;
175
176         cp = (guint32) c;
177         for (i = 0; i < simple_titlecase_mapping_count; i++) {
178                 if (simple_titlecase_mapping [i].codepoint == cp)
179                         return simple_titlecase_mapping [i].title;
180                 if (simple_titlecase_mapping [i].codepoint > cp)
181                         /* it is ordered, hence no more match */
182                         break;
183         }
184         return g_unichar_toupper (c);
185 }
186
187 gboolean
188 g_unichar_isxdigit (gunichar c)
189 {
190         return (g_unichar_xdigit_value (c) != -1);
191
192 }
193
194 gint
195 g_unichar_xdigit_value (gunichar c)
196 {
197         if (c >= 0x30 && c <= 0x39) /*0-9*/
198                 return (c - 0x30);
199         if (c >= 0x41 && c <= 0x46) /*A-F*/
200                 return (c - 0x37);
201         if (c >= 0x61 && c <= 0x66) /*a-f*/
202                 return (c - 0x57);
203         return -1;
204 }
205
206 gchar *
207 g_convert (const gchar *str, gssize len,
208            const gchar *to_codeset, const gchar *from_codeset,
209            gsize *bytes_read, gsize *bytes_written, GError **error)
210 {
211         char *result = NULL;
212 #ifdef G_OS_WIN32
213 #elif HAVE_ICONV_H
214         iconv_t convertor;
215         char *buffer, *output;
216         const char *strptr = (const char *) str;
217         size_t str_len = len == -1 ? strlen (str) : len;
218         size_t buffer_size;
219         size_t left, out_left;
220         
221         convertor = iconv_open (to_codeset, from_codeset);
222         if (convertor == (iconv_t) -1){
223                 if (bytes_written)
224                         *bytes_written = 0;
225                 if (bytes_read)
226                         *bytes_read = 0;
227                 return NULL;
228         }
229
230         buffer_size = str_len + 1 + 8;
231         buffer = g_malloc (buffer_size);
232         out_left = str_len;
233         output = buffer;
234         left = str_len;
235         while (left > 0){
236                 int res = iconv (convertor, (char **) &strptr, &left, &output, &out_left);
237                 if (res == (size_t) -1){
238                         if (errno == E2BIG){
239                                 char *n;
240                                 size_t extra_space = 8 + left;
241                                 size_t output_used = output - buffer;
242                                 
243                                 buffer_size += extra_space;
244                                 
245                                 n = g_realloc (buffer, buffer_size);
246                                 
247                                 if (n == NULL){
248                                         if (error != NULL)
249                                                 *error = g_error_new (NULL, G_CONVERT_ERROR_FAILED, "No memory left");
250                                         g_free (buffer);
251                                         result = NULL;
252                                         goto leave;
253                                 }
254                                 buffer = n;
255                                 out_left += extra_space;
256                                 output = buffer + output_used;
257                         } else if (errno == EILSEQ){
258                                 if (error != NULL)
259                                         *error = g_error_new (NULL, G_CONVERT_ERROR_ILLEGAL_SEQUENCE, "Invalid multi-byte sequence on input");
260                                 result = NULL;
261                                 g_free (buffer);
262                                 goto leave;
263                         } else if (errno == EINVAL){
264                                 if (error != NULL)
265                                         *error = g_error_new (NULL, G_CONVERT_ERROR_PARTIAL_INPUT, "Partial character sequence");
266                                 result = NULL;
267                                 g_free (buffer);
268                                 goto leave;
269                         }
270                 } 
271         }
272         if (bytes_read != NULL)
273                 *bytes_read = strptr - str;
274         if (bytes_written != NULL)
275                 *bytes_written = output - buffer;
276         *output = 0;
277         result = buffer;
278  leave:
279         iconv_close (convertor);
280 #endif
281         return result;
282 }
283
284 /*
285  * This is broken, and assumes an UTF8 system, but will do for eglib's first user
286  */
287 gchar *
288 g_filename_from_utf8 (const gchar *utf8string, gssize len, gsize *bytes_read, gsize *bytes_written, GError **error)
289 {
290         char *res;
291         
292         if (len == -1)
293                 len = strlen (utf8string);
294
295         res = g_malloc (len + 1);
296         g_strlcpy (res, utf8string, len + 1);
297         return res;
298 }
299
300 gboolean
301 g_get_charset (G_CONST_RETURN char **charset)
302 {
303 #ifdef G_OS_WIN32
304         static char buf[14];
305         sprintf (buf, "CP%u", GetACP ());
306         *charset = buf;
307         is_utf8 = FALSE;
308 #else
309         if (my_charset == NULL){
310                 /* These shouldn't be heap allocated */
311 #if HAVE_LOCALCHARSET_H
312                 my_charset = locale_charset ();
313 #elif defined(HAVE_LANGINFO_H)
314                 my_charset = nl_langinfo (CODESET);
315 #else
316                 my_charset = "UTF-8";
317 #endif
318                 is_utf8 = strcmp (my_charset, "UTF-8") == 0;
319         }
320         
321         if (charset != NULL)
322                 *charset = my_charset;
323
324 #endif
325         return is_utf8;
326 }
327
328 gchar *
329 g_locale_to_utf8 (const gchar *opsysstring, gssize len, gsize *bytes_read, gsize *bytes_written, GError **error)
330 {
331         g_get_charset (NULL);
332
333         return g_convert (opsysstring, len, "UTF-8", my_charset, bytes_read, bytes_written, error);
334 }
335
336 gchar *
337 g_locale_from_utf8 (const gchar *utf8string, gssize len, gsize *bytes_read, gsize *bytes_written, GError **error)
338 {
339         g_get_charset (NULL);
340
341         return g_convert (utf8string, len, my_charset, "UTF-8", bytes_read, bytes_written, error);
342 }
343 /**
344  * g_utf8_validate
345  * @utf: Pointer to putative UTF-8 encoded string.
346  *
347  * Checks @utf for being valid UTF-8. @utf is assumed to be
348  * null-terminated. This function is not super-strict, as it will
349  * allow longer UTF-8 sequences than necessary. Note that Java is
350  * capable of producing these sequences if provoked. Also note, this
351  * routine checks for the 4-byte maximum size, but does not check for
352  * 0x10ffff maximum value.
353  *
354  * Return value: true if @utf is valid.
355  **/
356 gboolean
357 g_utf8_validate (const gchar *str, gssize max_len, const gchar **end)
358 {
359         gssize byteCount = 0;
360         gboolean retVal = TRUE;
361         gboolean lastRet = TRUE;
362         guchar* ptr = (guchar*) str;
363         guint length;
364         guchar a;
365         guchar* srcPtr;
366         if (max_len == 0)
367                 return 0;
368         else if (max_len < 0)
369                 byteCount = max_len;
370         while (*ptr != 0 && byteCount <= max_len) {
371                 length = g_trailingBytesForUTF8 [*ptr] + 1;
372                 srcPtr = (guchar*) ptr + length;
373                 switch (length) {
374                 default: retVal = FALSE;
375                 /* Everything else falls through when "TRUE"... */
376                 case 4: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
377                                 if ((a == (guchar) 0xBF || a == (guchar) 0xBE) && *(srcPtr-1) == (guchar) 0xBF) {
378                                 if (*(srcPtr-2) == (guchar) 0x8F || *(srcPtr-2) == (guchar) 0x9F ||
379                                         *(srcPtr-2) == (guchar) 0xAF || *(srcPtr-2) == (guchar) 0xBF)
380                                         retVal = FALSE;
381                                 }
382                 case 3: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
383                 case 2: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
384
385                 switch (*ptr) {
386                 /* no fall-through in this inner switch */
387                 case 0xE0: if (a < (guchar) 0xA0) retVal = FALSE; break;
388                 case 0xED: if (a > (guchar) 0x9F) retVal = FALSE; break;
389                 case 0xEF: if (a == (guchar)0xB7 && (*(srcPtr+1) > (guchar) 0x8F && *(srcPtr+1) < 0xB0)) retVal = FALSE;
390                                    if (a == (guchar)0xBF && (*(srcPtr+1) == (guchar) 0xBE || *(srcPtr+1) == 0xBF)) retVal = FALSE; break;
391                 case 0xF0: if (a < (guchar) 0x90) retVal = FALSE; break;
392                 case 0xF4: if (a > (guchar) 0x8F) retVal = FALSE; break;
393                 default:   if (a < (guchar) 0x80) retVal = FALSE;
394                 }
395
396                 case 1: if (*ptr >= (guchar ) 0x80 && *ptr < (guchar) 0xC2) retVal = FALSE;
397                 }
398                 if (*ptr > (guchar) 0xF4)
399                         retVal = FALSE;
400                 //If the string is invalid, set the end to the invalid byte.
401                 if (!retVal && lastRet) {
402                         if (end != NULL)
403                                 *end = (gchar*) ptr;
404                         lastRet = FALSE;
405                 }
406                 ptr += length;
407                 if(max_len > 0)
408                         byteCount += length;
409         }
410         if (retVal && end != NULL)
411                 *end = (gchar*) ptr;
412         return retVal;
413 }
414 /**
415  * g_utf8_get_char
416  * @src: Pointer to UTF-8 encoded character.
417  *
418  * Return value: UTF-16 value of @src
419  **/
420 gunichar
421 g_utf8_get_char (const gchar *src)
422 {
423         gunichar ch = 0;
424         guchar* ptr = (guchar*) src;
425         gushort extraBytesToRead = g_trailingBytesForUTF8 [*ptr];
426
427         switch (extraBytesToRead) {
428         case 5: ch += *ptr++; ch <<= 6; // remember, illegal UTF-8
429         case 4: ch += *ptr++; ch <<= 6; // remember, illegal UTF-8
430         case 3: ch += *ptr++; ch <<= 6;
431         case 2: ch += *ptr++; ch <<= 6;
432         case 1: ch += *ptr++; ch <<= 6;
433         case 0: ch += *ptr;
434         }
435         ch -= offsetsFromUTF8 [extraBytesToRead];
436         return ch;
437 }
438 glong
439 g_utf8_strlen (const gchar *str, gssize max)
440 {
441         gssize byteCount = 0;
442         guchar* ptr = (guchar*) str;
443         glong length = 0;
444         if (max == 0)
445                 return 0;
446         else if (max < 0)
447                 byteCount = max;
448         while (*ptr != 0 && byteCount <= max) {
449                 gssize cLen = g_trailingBytesForUTF8 [*ptr] + 1;
450                 if (max > 0 && (byteCount + cLen) > max)
451                         return length;
452                 ptr += cLen;
453                 length++;
454                 if (max > 0)
455                         byteCount += cLen;
456         }
457         return length;
458 }