2002-04-30 Jeffrey Stedfast <fejj@ximian.com>
[mono.git] / mono / metadata / unicode.c
1 /*
2  * unicode.h: Unicode support
3  *
4  * Author:
5  *      Dietmar Maurer (dietmar@ximian.com)
6  *
7  * (C) 2001 Ximian, Inc.
8  */
9
10 #include <config.h>
11 #include <glib.h>
12 #include <errno.h>
13
14 #include <mono/metadata/object.h>
15 #include <mono/metadata/unicode.h>
16
17 #if HAVE_ICONV_H
18 #include <iconv.h> 
19 #elif HAVE_GICONV_H
20 #include <giconv.h> 
21 #endif
22
23 static MonoUnicodeCategory catmap[] = {
24         /* G_UNICODE_CONTROL = */              Control,
25         /* G_UNICODE_FORMAT = */               Format,
26         /* G_UNICODE_UNASSIGNED = */           OtherNotAssigned,
27         /* G_UNICODE_PRIVATE_USE = */          PrivateUse,
28         /* G_UNICODE_SURROGATE = */            Surrogate,
29         /* G_UNICODE_LOWERCASE_LETTER = */     LowercaseLetter,
30         /* G_UNICODE_MODIFIER_LETTER = */      ModifierLetter,
31         /* G_UNICODE_OTHER_LETTER = */         OtherLetter,
32         /* G_UNICODE_TITLECASE_LETTER = */     TitlecaseLetter,
33         /* G_UNICODE_UPPERCASE_LETTER = */     UppercaseLetter,
34         /* G_UNICODE_COMBINING_MARK = */       SpaceCombiningMark,
35         /* G_UNICODE_ENCLOSING_MARK = */       EnclosingMark,
36         /* G_UNICODE_NON_SPACING_MARK = */     NonSpacingMark,
37         /* G_UNICODE_DECIMAL_NUMBER = */       DecimalDigitNumber,
38         /* G_UNICODE_LETTER_NUMBER = */        LetterNumber,
39         /* G_UNICODE_OTHER_NUMBER = */         OtherNumber,
40         /* G_UNICODE_CONNECT_PUNCTUATION = */  ConnectorPunctuation,
41         /* G_UNICODE_DASH_PUNCTUATION = */     DashPunctuation,
42         /* G_UNICODE_CLOSE_PUNCTUATION = */    ClosePunctuation,
43         /* G_UNICODE_FINAL_PUNCTUATION = */    FinalQuotePunctuation,
44         /* G_UNICODE_INITIAL_PUNCTUATION = */  InitialQuotePunctuation,
45         /* G_UNICODE_OTHER_PUNCTUATION = */    OtherPunctuation,
46         /* G_UNICODE_OPEN_PUNCTUATION = */     OpenPunctuation,
47         /* G_UNICODE_CURRENCY_SYMBOL = */      CurrencySymbol,
48         /* G_UNICODE_MODIFIER_SYMBOL = */      ModifierSymbol,
49         /* G_UNICODE_MATH_SYMBOL = */          MathSymbol,
50         /* G_UNICODE_OTHER_SYMBOL = */         OtherSymbol,
51         /* G_UNICODE_LINE_SEPARATOR = */       LineSeperator,
52         /* G_UNICODE_PARAGRAPH_SEPARATOR = */  ParagraphSeperator,
53         /* G_UNICODE_SPACE_SEPARATOR = */      SpaceSeperator,
54 };
55
56 double 
57 ves_icall_System_Char_GetNumericValue (gunichar2 c)
58 {
59         return (double)g_unichar_digit_value (c);
60 }
61
62 MonoUnicodeCategory 
63 ves_icall_System_Char_GetUnicodeCategory (gunichar2 c)
64 {
65         return catmap [g_unichar_type (c)];
66 }
67
68 gboolean 
69 ves_icall_System_Char_IsControl (gunichar2 c)
70 {
71         return g_unichar_iscntrl (c);
72 }
73
74 gboolean 
75 ves_icall_System_Char_IsDigit (gunichar2 c)
76 {
77         return g_unichar_isdigit (c);
78 }
79
80 gboolean 
81 ves_icall_System_Char_IsLetter (gunichar2 c)
82 {
83         return g_unichar_isalpha (c);
84 }
85
86 gboolean 
87 ves_icall_System_Char_IsLower (gunichar2 c)
88 {
89         return g_unichar_islower (c);
90 }
91
92 gboolean 
93 ves_icall_System_Char_IsUpper (gunichar2 c)
94 {
95         return g_unichar_isupper (c);
96 }
97
98 gboolean 
99 ves_icall_System_Char_IsNumber (gunichar2 c)
100 {
101         return g_unichar_isxdigit (c);
102 }
103
104 gboolean 
105 ves_icall_System_Char_IsPunctuation (gunichar2 c)
106 {
107         return g_unichar_ispunct (c);
108 }
109
110 gboolean 
111 ves_icall_System_Char_IsSeparator (gunichar2 c)
112 {
113         GUnicodeType t = g_unichar_type (c);
114
115         return (t == G_UNICODE_LINE_SEPARATOR ||
116                 t == G_UNICODE_PARAGRAPH_SEPARATOR ||
117                 t == G_UNICODE_SPACE_SEPARATOR);
118 }
119
120 gboolean 
121 ves_icall_System_Char_IsSurrogate (gunichar2 c)
122 {
123         return (g_unichar_type (c) == G_UNICODE_SURROGATE);
124 }
125
126 gboolean 
127 ves_icall_System_Char_IsSymbol (gunichar2 c)
128 {
129         GUnicodeType t = g_unichar_type (c);
130
131         return (t == G_UNICODE_CURRENCY_SYMBOL ||
132                 t == G_UNICODE_MODIFIER_SYMBOL ||
133                 t == G_UNICODE_MATH_SYMBOL ||
134                 t == G_UNICODE_OTHER_SYMBOL);
135 }
136
137 gboolean 
138 ves_icall_System_Char_IsWhiteSpace (gunichar2 c)
139 {
140         return g_unichar_isspace (c);
141 }
142
143 gunichar2
144 ves_icall_System_Char_ToLower (gunichar2 c)
145 {
146         return g_unichar_tolower (c);
147 }
148
149 gunichar2
150 ves_icall_System_Char_ToUpper (gunichar2 c)
151 {
152         return g_unichar_toupper (c);
153 }
154
155 gpointer
156 ves_icall_iconv_new_encoder (MonoString *name, MonoBoolean big_endian)
157 {
158         iconv_t cd;
159         char *n;
160
161         // fixme: don't enforce big endian, support old iconv
162
163         g_assert (name);
164
165         n = mono_string_to_utf8 (name);
166
167         /* force big endian before class libraries are fixed */
168 #if G_BYTE_ORDER != G_LITTLE_ENDIAN
169         big_endian = 1;
170 #endif 
171
172 #ifdef HAVE_NEW_ICONV
173         cd = iconv_open (n, big_endian ? "UTF-16be" : "UTF-16le");
174 #else
175         cd = iconv_open (n, "UTF-16");
176 #endif
177         g_assert (cd != (iconv_t)-1);
178         g_free (n);
179
180         return (gpointer)cd;
181 }
182
183 gpointer
184 ves_icall_iconv_new_decoder (MonoString *name, MonoBoolean big_endian)
185 {
186         iconv_t cd;
187         char *n;
188
189         // fixme: don't enforce big endian, support old iconv
190
191         g_assert (name);
192
193         n = mono_string_to_utf8 (name);
194
195         /* force big endian before class libraries are fixed */
196 #if G_BYTE_ORDER != G_LITTLE_ENDIAN
197         big_endian = 1;
198 #endif 
199
200 #ifdef HAVE_NEW_ICONV
201         cd = iconv_open (big_endian ? "UTF-16be" : "UTF-16le", n);
202 #else
203         cd = iconv_open ("UTF-16", n);
204 #endif
205         g_assert (cd != (iconv_t)-1);
206         g_free (n);
207
208         return (gpointer)cd;
209 }
210
211 void
212 ves_icall_iconv_reset (gpointer converter)
213 {
214         iconv_t cd = (iconv_t)converter;
215
216         g_assert (cd);
217
218         iconv(cd, NULL, NULL, NULL, NULL);      
219 }
220
221 static int
222 iconv_get_length (iconv_t cd, guchar *src, int len, gboolean encode)
223 {
224         guchar buf [512];
225         int res;
226         guchar *outp;
227         guchar *p;
228         guint inbytes_remaining;
229         guint outbytes_remaining;
230         guint outbuf_size;
231         gboolean have_error = FALSE;
232         size_t err;
233         
234         g_assert (cd);
235         g_assert (src);
236
237 #ifndef HAVE_NEW_ICONV
238         if (G_BYTE_ORDER == G_LITTLE_ENDIAN && encode) {
239                 int i;
240                 
241                 src = g_memdup (src, len);
242                 for (i = 0; i < len; i += 2) {
243                         char t = src [i];
244                         src [i] = src [i + 1];
245                         src [i + 1] = t;
246                 }
247         }
248 #endif
249
250         p = src;
251         inbytes_remaining = len;
252         res = 0;
253         
254 again:
255         outbuf_size = 512;
256         outbytes_remaining = outbuf_size;
257         outp = buf;
258
259         err = iconv (cd, (char **)&p, &inbytes_remaining, 
260                      (char **)&outp, &outbytes_remaining);
261
262         if(err == (size_t)-1) {
263                 switch(errno) {
264                 case EINVAL:
265                         /* Incomplete text, do not report an error */
266                         break;
267                 case E2BIG: {
268                         res += outp - buf;
269                         goto again;
270                 }
271                 case EILSEQ:
272                         have_error = TRUE;
273                         break;
274                 default:
275                         have_error = TRUE;
276                         break;
277                 }
278         }
279   
280         res += outp - buf;
281
282         if((p - src) != len) {
283                 if(!have_error) {
284                         have_error = TRUE;
285                 }
286         }
287
288 #ifndef HAVE_NEW_ICONV
289         if (G_BYTE_ORDER == G_LITTLE_ENDIAN && encode)
290                 g_free (src);
291 #endif
292
293         if (have_error) {
294                 g_assert_not_reached ();
295                 return 0;
296         } else {
297                 return res;
298         }
299 }
300
301 int
302 ves_icall_iconv_get_byte_count (gpointer converter, MonoArray *chars, gint32 idx, gint32 count)
303 {
304         iconv_t cd = (iconv_t)converter;
305         guchar *src;
306         int len;
307
308         g_assert (cd);
309         g_assert (chars);
310         g_assert (mono_array_length (chars) > idx);
311         g_assert (mono_array_length (chars) >= (idx + count));
312
313         if (!(len = (mono_array_length (chars) - idx) * 2))
314                 return 0;
315
316         src =  mono_array_addr (chars, guint16, idx);
317
318         return iconv_get_length (cd, src, len, TRUE);
319 }
320
321 static int
322 iconv_convert (iconv_t cd, guchar *src, int len, guchar *dest, int max_len, gboolean encode)
323 {
324         guchar *p, *outp;
325         guint inbytes_remaining;
326         guint outbytes_remaining;
327         guint outbuf_size;
328         gboolean have_error = FALSE;
329         size_t err;
330
331         g_assert (cd);
332         g_assert (src);
333         g_assert (dest);
334
335 #ifndef HAVE_NEW_ICONV
336         if (G_BYTE_ORDER == G_LITTLE_ENDIAN && encode) {
337                 int i;
338                 
339                 src = g_memdup (src, len);
340                 for (i = 0; i < len; i += 2) {
341                         char t = src [i];
342                         src [i] = src [i + 1];
343                         src [i + 1] = t;
344                 }
345         }
346 #endif
347
348         p = src;
349         inbytes_remaining = len;
350         outbuf_size = max_len;
351   
352         outbytes_remaining = outbuf_size;
353         outp = dest;
354
355         err = iconv (cd, (char **)&p, &inbytes_remaining, (char **)&outp, &outbytes_remaining);
356
357         if(err == (size_t)-1) {
358                 if (errno == EINVAL) {
359                         /* Incomplete text, do not report an error */
360                 } else {
361                         have_error = TRUE;
362                 }
363         }
364
365         if ((p - src) != len) {
366                 if (!have_error) {
367                         have_error = TRUE;
368                 }
369         }
370
371 #ifndef HAVE_NEW_ICONV
372         if (G_BYTE_ORDER == G_LITTLE_ENDIAN) {
373                 if (encode) {
374                         g_free (src);
375                 } else {
376                         int mb = max_len - outbytes_remaining;
377                         int i;
378                         for (i = 0; i < mb; i+=2) {
379                                 char t = dest [i];
380                                 dest [i] = dest [i + 1];
381                                 dest [i + 1] = t;
382                         }
383                 }
384 }
385 #endif
386         if (have_error) {
387                 g_assert_not_reached ();
388                 return 0;
389         } else {
390                 /* we return the number of bytes written in dest */
391                 return max_len - outbytes_remaining;
392         }
393 }
394
395 int
396 ves_icall_iconv_get_bytes (gpointer converter, MonoArray *chars, gint32 charIndex, gint32 charCount,
397                            MonoArray *bytes, gint32 byteIndex)
398 {
399         iconv_t cd = (iconv_t)converter;
400         guchar *src, *dest;
401         int len, max_len;
402
403         if (!charCount)
404                 return 0;
405
406         g_assert (cd);
407         g_assert (chars);
408         g_assert (bytes);
409         g_assert (mono_array_length (chars) > charIndex);
410         g_assert (mono_array_length (chars) >= (charIndex + charCount));
411         g_assert (mono_array_length (bytes) > byteIndex);
412         g_assert (mono_array_length (chars) >= (byteIndex + charCount));
413
414         if (!(len = (mono_array_length (chars) - charIndex) * 2))
415                 return 0;
416
417         src =  mono_array_addr (chars, guint16, charIndex);
418         dest = mono_array_addr (bytes, char, byteIndex);
419
420         max_len = mono_array_length (bytes) - byteIndex;
421
422         return iconv_convert (cd, src, len, dest, max_len, TRUE);
423 }
424
425 int
426 ves_icall_iconv_get_char_count (gpointer converter, MonoArray *bytes, gint32 idx, gint32 count)
427 {
428         iconv_t cd = (iconv_t)converter;
429         guchar *src;
430
431         g_assert (cd);
432         g_assert (bytes);
433         g_assert (mono_array_length (bytes) > idx);
434         g_assert (mono_array_length (bytes) >= (idx + count));
435
436         src =  mono_array_addr (bytes, char, idx);
437
438         /* iconv_get_length () returns the number of bytes */
439         return iconv_get_length (cd, src, (int) count, FALSE) / 2;
440 }
441
442 int
443 ves_icall_iconv_get_chars (gpointer converter, MonoArray *bytes, gint32 byteIndex, gint32 byteCount,
444                            MonoArray *chars, gint32 charIndex)
445 {
446         iconv_t cd = (iconv_t)converter;
447         guchar *src, *dest;
448         int max_len;
449
450         g_assert (cd);
451         g_assert (chars);
452         g_assert (bytes);
453         g_assert (mono_array_length (bytes) > byteIndex);
454         g_assert (mono_array_length (chars) >= (byteIndex + byteCount));
455         g_assert (mono_array_length (chars) > charIndex);
456
457         src =  mono_array_addr (bytes, char, byteIndex);
458         dest = mono_array_addr (chars, guint16, charIndex);
459
460         max_len = (mono_array_length (chars) - charIndex) * 2;
461
462         /* iconv_convert () returns the number of bytes */
463         return iconv_convert (cd, src, (int) byteCount, dest, max_len, FALSE) / 2;
464 }