Another soptimization for g_convert()
[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 #else
47 #    ifdef HAVE_LANGINFO_H
48 #       include <langinfo.h>
49 #    endif
50 #    ifdef HAVE_LOCALCHARSET_H
51 #       include <localcharset.h>
52 #    endif
53 #endif
54
55 static char *my_charset;
56 static gboolean is_utf8;
57
58 /*
59  * Character set conversion
60  */
61 /*
62 * Index into the table below with the first byte of a UTF-8 sequence to
63 * get the number of trailing bytes that are supposed to follow it.
64 * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is
65 * left as-is for anyone who may want to do such conversion, which was
66 * allowed in earlier algorithms.
67 */
68 const gchar g_trailingBytesForUTF8 [256] = {
69         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,
70         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,
71         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,
72         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,
73         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,
74         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,
75         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,
76         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
77 };
78
79 /*
80 * Magic values subtracted from a buffer value during UTF8 conversion.
81 * This table contains as many values as there might be trailing bytes
82 * in a UTF-8 sequence.
83 */
84 static const gulong offsetsFromUTF8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL,
85 0x03C82080UL, 0xFA082080UL, 0x82082080UL };
86
87 GUnicodeType 
88 g_unichar_type (gunichar c)
89 {
90         int i;
91
92         guint16 cp = (guint16) c;
93         for (i = 0; i < unicode_category_ranges_count; i++) {
94                 if (cp < unicode_category_ranges [i].start)
95                         continue;
96                 if (unicode_category_ranges [i].end <= cp)
97                         continue;
98                 return unicode_category [i] [cp - unicode_category_ranges [i].start];
99         }
100
101         /*
102         // 3400-4DB5: OtherLetter
103         // 4E00-9FC3: OtherLetter
104         // AC00-D7A3: OtherLetter
105         // D800-DFFF: OtherSurrogate
106         // E000-F8FF: OtherPrivateUse
107         // 20000-2A6D6 OtherLetter
108         // F0000-FFFFD OtherPrivateUse
109         // 100000-10FFFD OtherPrivateUse
110         */
111         if (0x3400 <= cp && cp < 0x4DB5)
112                 return G_UNICODE_OTHER_LETTER;
113         if (0x4E00 <= cp && cp < 0x9FC3)
114                 return G_UNICODE_OTHER_LETTER;
115         if (0xAC00<= cp && cp < 0xD7A3)
116                 return G_UNICODE_OTHER_LETTER;
117         if (0xD800 <= cp && cp < 0xDFFF)
118                 return G_UNICODE_SURROGATE;
119         if (0xE000 <= cp && cp < 0xF8FF)
120                 return G_UNICODE_PRIVATE_USE;
121         /* since the argument is UTF-16, we cannot check beyond FFFF */
122
123         /* It should match any of above */
124         return 0;
125 }
126
127 GUnicodeBreakType
128 g_unichar_break_type (gunichar c)
129 {
130         // MOONLIGHT_FIXME
131         return G_UNICODE_BREAK_UNKNOWN;
132 }
133
134 gunichar
135 g_unichar_case (gunichar c, gboolean upper)
136 {
137         gint8 i, i2;
138         guint32 cp = (guint32) c, v;
139
140         for (i = 0; i < simple_case_map_ranges_count; i++) {
141                 if (cp < simple_case_map_ranges [i].start)
142                         return c;
143                 if (simple_case_map_ranges [i].end <= cp)
144                         continue;
145                 if (c < 0x10000) {
146                         const guint16 *tab = upper ? simple_upper_case_mapping_lowarea [i] : simple_lower_case_mapping_lowarea [i];
147                         v = tab [cp - simple_case_map_ranges [i].start];
148                 } else {
149                         const guint32 *tab;
150                         i2 = (gint8)(i - (upper ? simple_upper_case_mapping_lowarea_table_count : simple_lower_case_mapping_lowarea_table_count));
151                         tab = upper ? simple_upper_case_mapping_higharea [i2] : simple_lower_case_mapping_higharea [i2];
152                         v = tab [cp - simple_case_map_ranges [i].start];
153                 }
154                 return v != 0 ? (gunichar) v : c;
155         }
156         return c;
157 }
158
159 gunichar
160 g_unichar_toupper (gunichar c)
161 {
162         return g_unichar_case (c, TRUE);
163 }
164
165 gunichar
166 g_unichar_tolower (gunichar c)
167 {
168         return g_unichar_case (c, FALSE);
169 }
170
171 gunichar
172 g_unichar_totitle (gunichar c)
173 {
174         guint8 i;
175         guint32 cp;
176
177         cp = (guint32) c;
178         for (i = 0; i < simple_titlecase_mapping_count; i++) {
179                 if (simple_titlecase_mapping [i].codepoint == cp)
180                         return simple_titlecase_mapping [i].title;
181                 if (simple_titlecase_mapping [i].codepoint > cp)
182                         /* it is ordered, hence no more match */
183                         break;
184         }
185         return g_unichar_toupper (c);
186 }
187
188 gboolean
189 g_unichar_isxdigit (gunichar c)
190 {
191         return (g_unichar_xdigit_value (c) != -1);
192
193 }
194
195 gint
196 g_unichar_xdigit_value (gunichar c)
197 {
198         if (c >= 0x30 && c <= 0x39) /*0-9*/
199                 return (c - 0x30);
200         if (c >= 0x41 && c <= 0x46) /*A-F*/
201                 return (c - 0x37);
202         if (c >= 0x61 && c <= 0x66) /*a-f*/
203                 return (c - 0x57);
204         return -1;
205 }
206
207 gboolean
208 g_unichar_isspace (gunichar c)
209 {
210         GUnicodeType type = g_unichar_type (c);
211         if (type == G_UNICODE_LINE_SEPARATOR ||
212             type == G_UNICODE_PARAGRAPH_SEPARATOR ||
213             type == G_UNICODE_SPACE_SEPARATOR)
214                 return TRUE;
215
216         return FALSE;
217 }
218
219 gchar *
220 g_convert (const gchar *str, gssize len, const gchar *to_charset, const gchar *from_charset,
221            gsize *bytes_read, gsize *bytes_written, GError **err)
222 {
223         size_t outsize, outused, outleft, inleft, grow, rc;
224         char *result, *outbuf, *inbuf;
225         gboolean flush = FALSE;
226         gboolean done = FALSE;
227         GIConv cd;
228         
229         g_return_val_if_fail (str != NULL, NULL);
230         g_return_val_if_fail (to_charset != NULL, NULL);
231         g_return_val_if_fail (from_charset != NULL, NULL);
232         
233         if ((cd = g_iconv_open (to_charset, from_charset)) == (GIConv) -1) {
234                 g_set_error (err, G_CONVERT_ERROR, G_CONVERT_ERROR_NO_CONVERSION, g_strerror (ENOTSUP));
235                 
236                 if (bytes_written)
237                         *bytes_written = 0;
238                 
239                 if (bytes_read)
240                         *bytes_read = 0;
241                 
242                 return NULL;
243         }
244         
245         inleft = len < 0 ? strlen (str) : len;
246         inbuf = (char *) str;
247         
248         outleft = outsize = MAX (inleft, 8);
249         outbuf = result = g_malloc (outsize + 4);
250         
251         do {
252                 if (!flush)
253                         rc = g_iconv (cd, &inbuf, &inleft, &outbuf, &outleft);
254                 else
255                         rc = g_iconv (cd, NULL, NULL, &outbuf, &outleft);
256                 
257                 if (rc == (size_t) -1) {
258                         switch (errno) {
259                         case E2BIG:
260                                 /* grow our result buffer */
261                                 grow = MAX (inleft, 8) << 1;
262                                 outused = outbuf - result;
263                                 outsize += grow;
264                                 outleft += grow;
265                                 result = g_realloc (result, outsize + 4);
266                                 outbuf = result + outused;
267                                 break;
268                         case EINVAL:
269                                 /* incomplete input, stop converting and terminate here */
270                                 if (flush)
271                                         done = TRUE;
272                                 else
273                                         flush = TRUE;
274                                 break;
275                         case EILSEQ:
276                                 /* illegal sequence in the input */
277                                 g_set_error (err, G_CONVERT_ERROR, G_CONVERT_ERROR_ILLEGAL_SEQUENCE, "%s", g_strerror (errno));
278                                 
279                                 if (bytes_read) {
280                                         /* save offset of the illegal input sequence */
281                                         *bytes_read = (inbuf - str);
282                                 }
283                                 
284                                 if (bytes_written)
285                                         *bytes_written = 0;
286                                 
287                                 g_iconv_close (cd);
288                                 g_free (result);
289                                 return NULL;
290                         default:
291                                 /* unknown errno */
292                                 g_set_error (err, G_CONVERT_ERROR, G_CONVERT_ERROR_FAILED, "%s", g_strerror (errno));
293                                 
294                                 if (bytes_written)
295                                         *bytes_written = 0;
296                                 
297                                 if (bytes_read)
298                                         *bytes_read = 0;
299                                 
300                                 g_iconv_close (cd);
301                                 g_free (result);
302                                 return NULL;
303                         }
304                 } else if (flush) {
305                         /* input has been converted and output has been flushed */
306                         break;
307                 } else {
308                         /* input has been converted, need to flush the output */
309                         flush = TRUE;
310                 }
311         } while (!done);
312         
313         g_iconv_close (cd);
314         
315         /* Note: not all charsets can be null-terminated with a single
316            null byte. UCS2, for example, needs 2 null bytes and UCS4
317            needs 4. I hope that 4 null bytes is enough to terminate all
318            multibyte charsets? */
319         
320         /* null-terminate the result */
321         memset (outbuf, 0, 4);
322         
323         if (bytes_written)
324                 *bytes_written = outbuf - result;
325         
326         if (bytes_read)
327                 *bytes_read = inbuf - str;
328         
329         return result;
330 }
331
332 /*
333  * This is broken, and assumes an UTF8 system, but will do for eglib's first user
334  */
335 gchar *
336 g_filename_from_utf8 (const gchar *utf8string, gssize len, gsize *bytes_read, gsize *bytes_written, GError **error)
337 {
338         char *res;
339         
340         if (len == -1)
341                 len = strlen (utf8string);
342
343         res = g_malloc (len + 1);
344         g_strlcpy (res, utf8string, len + 1);
345         return res;
346 }
347
348 gboolean
349 g_get_charset (G_CONST_RETURN char **charset)
350 {
351         if (my_charset == NULL) {
352 #ifdef G_OS_WIN32
353                 static char buf [14];
354                 sprintf (buf, "CP%u", GetACP ());
355                 my_charset = buf;
356                 is_utf8 = FALSE;
357 #else
358                 /* These shouldn't be heap allocated */
359 #if HAVE_LOCALCHARSET_H
360                 my_charset = locale_charset ();
361 #elif defined(HAVE_LANGINFO_H)
362                 my_charset = nl_langinfo (CODESET);
363 #else
364                 my_charset = "UTF-8";
365 #endif
366                 is_utf8 = strcmp (my_charset, "UTF-8") == 0;
367 #endif
368         }
369         
370         if (charset != NULL)
371                 *charset = my_charset;
372
373         return is_utf8;
374 }
375
376 gchar *
377 g_locale_to_utf8 (const gchar *opsysstring, gssize len, gsize *bytes_read, gsize *bytes_written, GError **error)
378 {
379         g_get_charset (NULL);
380
381         return g_convert (opsysstring, len, "UTF-8", my_charset, bytes_read, bytes_written, error);
382 }
383
384 gchar *
385 g_locale_from_utf8 (const gchar *utf8string, gssize len, gsize *bytes_read, gsize *bytes_written, GError **error)
386 {
387         g_get_charset (NULL);
388
389         return g_convert (utf8string, len, my_charset, "UTF-8", bytes_read, bytes_written, error);
390 }
391 /**
392  * g_utf8_validate
393  * @utf: Pointer to putative UTF-8 encoded string.
394  *
395  * Checks @utf for being valid UTF-8. @utf is assumed to be
396  * null-terminated. This function is not super-strict, as it will
397  * allow longer UTF-8 sequences than necessary. Note that Java is
398  * capable of producing these sequences if provoked. Also note, this
399  * routine checks for the 4-byte maximum size, but does not check for
400  * 0x10ffff maximum value.
401  *
402  * Return value: true if @utf is valid.
403  **/
404 gboolean
405 g_utf8_validate (const gchar *str, gssize max_len, const gchar **end)
406 {
407         gssize byteCount = 0;
408         gboolean retVal = TRUE;
409         gboolean lastRet = TRUE;
410         guchar* ptr = (guchar*) str;
411         guint length;
412         guchar a;
413         guchar* srcPtr;
414         if (max_len == 0)
415                 return 0;
416         else if (max_len < 0)
417                 byteCount = max_len;
418         while (*ptr != 0 && byteCount <= max_len) {
419                 length = g_trailingBytesForUTF8 [*ptr] + 1;
420                 srcPtr = (guchar*) ptr + length;
421                 switch (length) {
422                 default: retVal = FALSE;
423                 /* Everything else falls through when "TRUE"... */
424                 case 4: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
425                                 if ((a == (guchar) 0xBF || a == (guchar) 0xBE) && *(srcPtr-1) == (guchar) 0xBF) {
426                                 if (*(srcPtr-2) == (guchar) 0x8F || *(srcPtr-2) == (guchar) 0x9F ||
427                                         *(srcPtr-2) == (guchar) 0xAF || *(srcPtr-2) == (guchar) 0xBF)
428                                         retVal = FALSE;
429                                 }
430                 case 3: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
431                 case 2: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
432
433                 switch (*ptr) {
434                 /* no fall-through in this inner switch */
435                 case 0xE0: if (a < (guchar) 0xA0) retVal = FALSE; break;
436                 case 0xED: if (a > (guchar) 0x9F) retVal = FALSE; break;
437                 case 0xEF: if (a == (guchar)0xB7 && (*(srcPtr+1) > (guchar) 0x8F && *(srcPtr+1) < 0xB0)) retVal = FALSE;
438                                    if (a == (guchar)0xBF && (*(srcPtr+1) == (guchar) 0xBE || *(srcPtr+1) == 0xBF)) retVal = FALSE; break;
439                 case 0xF0: if (a < (guchar) 0x90) retVal = FALSE; break;
440                 case 0xF4: if (a > (guchar) 0x8F) retVal = FALSE; break;
441                 default:   if (a < (guchar) 0x80) retVal = FALSE;
442                 }
443
444                 case 1: if (*ptr >= (guchar ) 0x80 && *ptr < (guchar) 0xC2) retVal = FALSE;
445                 }
446                 if (*ptr > (guchar) 0xF4)
447                         retVal = FALSE;
448                 //If the string is invalid, set the end to the invalid byte.
449                 if (!retVal && lastRet) {
450                         if (end != NULL)
451                                 *end = (gchar*) ptr;
452                         lastRet = FALSE;
453                 }
454                 ptr += length;
455                 if(max_len > 0)
456                         byteCount += length;
457         }
458         if (retVal && end != NULL)
459                 *end = (gchar*) ptr;
460         return retVal;
461 }
462 /**
463  * g_utf8_get_char
464  * @src: Pointer to UTF-8 encoded character.
465  *
466  * Return value: UTF-16 value of @src
467  **/
468 gunichar
469 g_utf8_get_char (const gchar *src)
470 {
471         gunichar ch = 0;
472         guchar* ptr = (guchar*) src;
473         gushort extraBytesToRead = g_trailingBytesForUTF8 [*ptr];
474
475         switch (extraBytesToRead) {
476         case 5: ch += *ptr++; ch <<= 6; // remember, illegal UTF-8
477         case 4: ch += *ptr++; ch <<= 6; // remember, illegal UTF-8
478         case 3: ch += *ptr++; ch <<= 6;
479         case 2: ch += *ptr++; ch <<= 6;
480         case 1: ch += *ptr++; ch <<= 6;
481         case 0: ch += *ptr;
482         }
483         ch -= offsetsFromUTF8 [extraBytesToRead];
484         return ch;
485 }
486 glong
487 g_utf8_strlen (const gchar *str, gssize max)
488 {
489         gssize byteCount = 0;
490         guchar* ptr = (guchar*) str;
491         glong length = 0;
492         if (max == 0)
493                 return 0;
494         else if (max < 0)
495                 byteCount = max;
496         while (*ptr != 0 && byteCount <= max) {
497                 gssize cLen = g_trailingBytesForUTF8 [*ptr] + 1;
498                 if (max > 0 && (byteCount + cLen) > max)
499                         return length;
500                 ptr += cLen;
501                 length++;
502                 if (max > 0)
503                         byteCount += cLen;
504         }
505         return length;
506 }