Implemented g_iconv() w/ needing libiconv
[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,
221            const gchar *to_codeset, const gchar *from_codeset,
222            gsize *bytes_read, gsize *bytes_written, GError **error)
223 {
224         size_t str_len = len == -1 ? strlen (str) : len;
225         const char *strptr = (const char *) str;
226         size_t left, out_left, buffer_size;
227         char *buffer, *output;
228         char *result = NULL;
229         GIConv cd;
230         
231         if ((cd = g_iconv_open (to_codeset, from_codeset)) == (GIConv) -1) {
232                 if (bytes_written)
233                         *bytes_written = 0;
234                 if (bytes_read)
235                         *bytes_read = 0;
236                 return NULL;
237         }
238         
239         buffer_size = str_len + 1 + 8;
240         buffer = g_malloc (buffer_size);
241         out_left = str_len;
242         output = buffer;
243         left = str_len;
244         
245         while (left > 0){
246                 int res = g_iconv (cd, (char **) &strptr, &left, &output, &out_left);
247                 if (res == (size_t) -1){
248                         if (errno == E2BIG){
249                                 char *n;
250                                 size_t extra_space = 8 + left;
251                                 size_t output_used = output - buffer;
252                                 
253                                 buffer_size += extra_space;
254                                 
255                                 n = g_realloc (buffer, buffer_size);
256                                 
257                                 if (n == NULL){
258                                         if (error != NULL)
259                                                 *error = g_error_new (NULL, G_CONVERT_ERROR_FAILED, "No memory left");
260                                         g_free (buffer);
261                                         result = NULL;
262                                         goto leave;
263                                 }
264                                 buffer = n;
265                                 out_left += extra_space;
266                                 output = buffer + output_used;
267                         } else if (errno == EILSEQ){
268                                 if (error != NULL)
269                                         *error = g_error_new (NULL, G_CONVERT_ERROR_ILLEGAL_SEQUENCE, "Invalid multi-byte sequence on input");
270                                 result = NULL;
271                                 g_free (buffer);
272                                 goto leave;
273                         } else if (errno == EINVAL){
274                                 if (error != NULL)
275                                         *error = g_error_new (NULL, G_CONVERT_ERROR_PARTIAL_INPUT, "Partial character sequence");
276                                 result = NULL;
277                                 g_free (buffer);
278                                 goto leave;
279                         }
280                 } 
281         }
282         if (bytes_read != NULL)
283                 *bytes_read = strptr - str;
284         if (bytes_written != NULL)
285                 *bytes_written = output - buffer;
286         *output = 0;
287         result = buffer;
288  leave:
289         g_iconv_close (cd);
290         
291         return result;
292 }
293
294 /*
295  * This is broken, and assumes an UTF8 system, but will do for eglib's first user
296  */
297 gchar *
298 g_filename_from_utf8 (const gchar *utf8string, gssize len, gsize *bytes_read, gsize *bytes_written, GError **error)
299 {
300         char *res;
301         
302         if (len == -1)
303                 len = strlen (utf8string);
304
305         res = g_malloc (len + 1);
306         g_strlcpy (res, utf8string, len + 1);
307         return res;
308 }
309
310 gboolean
311 g_get_charset (G_CONST_RETURN char **charset)
312 {
313         if (my_charset == NULL) {
314 #ifdef G_OS_WIN32
315                 static char buf [14];
316                 sprintf (buf, "CP%u", GetACP ());
317                 my_charset = buf;
318                 is_utf8 = FALSE;
319 #else
320                 /* These shouldn't be heap allocated */
321 #if HAVE_LOCALCHARSET_H
322                 my_charset = locale_charset ();
323 #elif defined(HAVE_LANGINFO_H)
324                 my_charset = nl_langinfo (CODESET);
325 #else
326                 my_charset = "UTF-8";
327 #endif
328                 is_utf8 = strcmp (my_charset, "UTF-8") == 0;
329 #endif
330         }
331         
332         if (charset != NULL)
333                 *charset = my_charset;
334
335         return is_utf8;
336 }
337
338 gchar *
339 g_locale_to_utf8 (const gchar *opsysstring, gssize len, gsize *bytes_read, gsize *bytes_written, GError **error)
340 {
341         g_get_charset (NULL);
342
343         return g_convert (opsysstring, len, "UTF-8", my_charset, bytes_read, bytes_written, error);
344 }
345
346 gchar *
347 g_locale_from_utf8 (const gchar *utf8string, gssize len, gsize *bytes_read, gsize *bytes_written, GError **error)
348 {
349         g_get_charset (NULL);
350
351         return g_convert (utf8string, len, my_charset, "UTF-8", bytes_read, bytes_written, error);
352 }
353 /**
354  * g_utf8_validate
355  * @utf: Pointer to putative UTF-8 encoded string.
356  *
357  * Checks @utf for being valid UTF-8. @utf is assumed to be
358  * null-terminated. This function is not super-strict, as it will
359  * allow longer UTF-8 sequences than necessary. Note that Java is
360  * capable of producing these sequences if provoked. Also note, this
361  * routine checks for the 4-byte maximum size, but does not check for
362  * 0x10ffff maximum value.
363  *
364  * Return value: true if @utf is valid.
365  **/
366 gboolean
367 g_utf8_validate (const gchar *str, gssize max_len, const gchar **end)
368 {
369         gssize byteCount = 0;
370         gboolean retVal = TRUE;
371         gboolean lastRet = TRUE;
372         guchar* ptr = (guchar*) str;
373         guint length;
374         guchar a;
375         guchar* srcPtr;
376         if (max_len == 0)
377                 return 0;
378         else if (max_len < 0)
379                 byteCount = max_len;
380         while (*ptr != 0 && byteCount <= max_len) {
381                 length = g_trailingBytesForUTF8 [*ptr] + 1;
382                 srcPtr = (guchar*) ptr + length;
383                 switch (length) {
384                 default: retVal = FALSE;
385                 /* Everything else falls through when "TRUE"... */
386                 case 4: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
387                                 if ((a == (guchar) 0xBF || a == (guchar) 0xBE) && *(srcPtr-1) == (guchar) 0xBF) {
388                                 if (*(srcPtr-2) == (guchar) 0x8F || *(srcPtr-2) == (guchar) 0x9F ||
389                                         *(srcPtr-2) == (guchar) 0xAF || *(srcPtr-2) == (guchar) 0xBF)
390                                         retVal = FALSE;
391                                 }
392                 case 3: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
393                 case 2: if ((a = (*--srcPtr)) < (guchar) 0x80 || a > (guchar) 0xBF) retVal = FALSE;
394
395                 switch (*ptr) {
396                 /* no fall-through in this inner switch */
397                 case 0xE0: if (a < (guchar) 0xA0) retVal = FALSE; break;
398                 case 0xED: if (a > (guchar) 0x9F) retVal = FALSE; break;
399                 case 0xEF: if (a == (guchar)0xB7 && (*(srcPtr+1) > (guchar) 0x8F && *(srcPtr+1) < 0xB0)) retVal = FALSE;
400                                    if (a == (guchar)0xBF && (*(srcPtr+1) == (guchar) 0xBE || *(srcPtr+1) == 0xBF)) retVal = FALSE; break;
401                 case 0xF0: if (a < (guchar) 0x90) retVal = FALSE; break;
402                 case 0xF4: if (a > (guchar) 0x8F) retVal = FALSE; break;
403                 default:   if (a < (guchar) 0x80) retVal = FALSE;
404                 }
405
406                 case 1: if (*ptr >= (guchar ) 0x80 && *ptr < (guchar) 0xC2) retVal = FALSE;
407                 }
408                 if (*ptr > (guchar) 0xF4)
409                         retVal = FALSE;
410                 //If the string is invalid, set the end to the invalid byte.
411                 if (!retVal && lastRet) {
412                         if (end != NULL)
413                                 *end = (gchar*) ptr;
414                         lastRet = FALSE;
415                 }
416                 ptr += length;
417                 if(max_len > 0)
418                         byteCount += length;
419         }
420         if (retVal && end != NULL)
421                 *end = (gchar*) ptr;
422         return retVal;
423 }
424 /**
425  * g_utf8_get_char
426  * @src: Pointer to UTF-8 encoded character.
427  *
428  * Return value: UTF-16 value of @src
429  **/
430 gunichar
431 g_utf8_get_char (const gchar *src)
432 {
433         gunichar ch = 0;
434         guchar* ptr = (guchar*) src;
435         gushort extraBytesToRead = g_trailingBytesForUTF8 [*ptr];
436
437         switch (extraBytesToRead) {
438         case 5: ch += *ptr++; ch <<= 6; // remember, illegal UTF-8
439         case 4: ch += *ptr++; ch <<= 6; // remember, illegal UTF-8
440         case 3: ch += *ptr++; ch <<= 6;
441         case 2: ch += *ptr++; ch <<= 6;
442         case 1: ch += *ptr++; ch <<= 6;
443         case 0: ch += *ptr;
444         }
445         ch -= offsetsFromUTF8 [extraBytesToRead];
446         return ch;
447 }
448 glong
449 g_utf8_strlen (const gchar *str, gssize max)
450 {
451         gssize byteCount = 0;
452         guchar* ptr = (guchar*) str;
453         glong length = 0;
454         if (max == 0)
455                 return 0;
456         else if (max < 0)
457                 byteCount = max;
458         while (*ptr != 0 && byteCount <= max) {
459                 gssize cLen = g_trailingBytesForUTF8 [*ptr] + 1;
460                 if (max > 0 && (byteCount + cLen) > max)
461                         return length;
462                 ptr += cLen;
463                 length++;
464                 if (max > 0)
465                         byteCount += cLen;
466         }
467         return length;
468 }