applied patch from Andreas Persenius <ndap@swipnet.se> that updates the
[platform/upstream/glib.git] / gutf8.c
1 /* gutf8.c - Operations on UTF-8 strings.
2  *
3  * Copyright (C) 1999 Tom Tromey
4  * Copyright (C) 2000 Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #include <config.h>
23
24 #include <stdlib.h>
25 #ifdef HAVE_CODESET
26 #include <langinfo.h>
27 #endif
28 #include <string.h>
29
30 #include "glib.h"
31
32 #define UTF8_COMPUTE(Char, Mask, Len)                                         \
33   if (Char < 128)                                                             \
34     {                                                                         \
35       Len = 1;                                                                \
36       Mask = 0x7f;                                                            \
37     }                                                                         \
38   else if ((Char & 0xe0) == 0xc0)                                             \
39     {                                                                         \
40       Len = 2;                                                                \
41       Mask = 0x1f;                                                            \
42     }                                                                         \
43   else if ((Char & 0xf0) == 0xe0)                                             \
44     {                                                                         \
45       Len = 3;                                                                \
46       Mask = 0x0f;                                                            \
47     }                                                                         \
48   else if ((Char & 0xf8) == 0xf0)                                             \
49     {                                                                         \
50       Len = 4;                                                                \
51       Mask = 0x07;                                                            \
52     }                                                                         \
53   else if ((Char & 0xfc) == 0xf8)                                             \
54     {                                                                         \
55       Len = 5;                                                                \
56       Mask = 0x03;                                                            \
57     }                                                                         \
58   else if ((Char & 0xfe) == 0xfc)                                             \
59     {                                                                         \
60       Len = 6;                                                                \
61       Mask = 0x01;                                                            \
62     }                                                                         \
63   else                                                                        \
64     Len = -1;
65
66 #define UTF8_GET(Result, Chars, Count, Mask, Len)                             \
67   (Result) = (Chars)[0] & (Mask);                                             \
68   for ((Count) = 1; (Count) < (Len); ++(Count))                               \
69     {                                                                         \
70       if (((Chars)[(Count)] & 0xc0) != 0x80)                                  \
71         {                                                                     \
72           (Result) = -1;                                                      \
73           break;                                                              \
74         }                                                                     \
75       (Result) <<= 6;                                                         \
76       (Result) |= ((Chars)[(Count)] & 0x3f);                                  \
77     }
78
79 gchar g_utf8_skip[256] = {
80   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,
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   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,
83   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,
84   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,
85   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,
86   2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
87   3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0
88 };
89
90 /**
91  * g_utf8_find_prev_char:
92  * @str: pointer to the beginning of a UTF-8 string
93  * @p: pointer to some position within @str
94  * 
95  * Given a position @p with a UTF-8 encoded string @str, find the start
96  * of the previous UTF-8 character starting before @p. Returns %NULL if no
97  * UTF-8 characters are present in @p before @str.
98  *
99  * @p does not have to be at the beginning of a UTF-8 chracter. No check
100  * is made to see if the character found is actually valid other than
101  * it starts with an appropriate byte.
102  *
103  * Return value: a pointer to the found character or %NULL.
104  **/
105 gchar *
106 g_utf8_find_prev_char (const char *str,
107                        const char *p)
108 {
109   for (--p; p > str; --p)
110     {
111       if ((*p & 0xc0) != 0x80)
112         return (gchar *)p;
113     }
114   return NULL;
115 }
116
117 /**
118  * g_utf8_find_next_char:
119  * @p: a pointer to a position within a UTF-8 encoded string
120  * @end: a pointer to the end of the string, or %NULL to indicate
121  *        that the string is NULL terminated, in which case
122  *        the returned value will be 
123  *
124  * Find the start of the next utf-8 character in the string after @p
125  *
126  * @p does not have to be at the beginning of a UTF-8 chracter. No check
127  * is made to see if the character found is actually valid other than
128  * it starts with an appropriate byte.
129  * 
130  * Return value: a pointer to the found character or %NULL
131  **/
132 gchar *
133 g_utf8_find_next_char (const gchar *p,
134                        const gchar *end)
135 {
136   if (*p)
137     {
138       if (end)
139         for (++p; p < end && (*p & 0xc0) == 0x80; ++p)
140           ;
141       else
142         for (++p; (*p & 0xc0) == 0x80; ++p)
143           ;
144     }
145   return (p == end) ? NULL : (gchar *)p;
146 }
147
148 /**
149  * g_utf8_prev_char:
150  * @p: a pointer to a position within a UTF-8 encoded string
151  *
152  * Find the previous UTF-8 character in the string before @p
153  *
154  * @p does not have to be at the beginning of a UTF-8 character. No check
155  * is made to see if the character found is actually valid other than
156  * it starts with an appropriate byte. If @p might be the first
157  * character of the string, you must use g_utf8_find_prev_char instead.
158  * 
159  * Return value: a pointer to the found character.
160  **/
161 gchar *
162 g_utf8_prev_char (const gchar *p)
163 {
164   while (TRUE)
165     {
166       p--;
167       if ((*p & 0xc0) != 0x80)
168         return (gchar *)p;
169     }
170 }
171
172 /**
173  * g_utf8_strlen:
174  * @p: pointer to the start of a UTF-8 string.
175  * @max: the maximum number of bytes to examine. If @max
176  *       is less than 0, then the string is assumed to be
177  *       nul-terminated.
178  * 
179  * Return value: the length of the string in characters
180  */
181 gint
182 g_utf8_strlen (const gchar *p, gint max)
183 {
184   int len = 0;
185   const gchar *start = p;
186   /* special case for the empty string */
187   if (!*p) 
188     return 0;
189   /* Note that the test here and the test in the loop differ subtly.
190      In the loop we want to see if we've passed the maximum limit --
191      for instance if the buffer ends mid-character.  Here at the top
192      of the loop we want to see if we've just reached the last byte.  */
193   while (max < 0 || p - start < max)
194     {
195       p = g_utf8_next_char (p);
196       ++len;
197       if (! *p || (max > 0 && p - start > max))
198         break;
199     }
200   return len;
201 }
202
203 /**
204  * g_utf8_get_char:
205  * @p: a pointer to unicode character encoded as UTF-8
206  * 
207  * Convert a sequence of bytes encoded as UTF-8 to a unicode character.
208  * 
209  * Return value: the resulting character or (gunichar)-1 if @p does
210  *               not point to a valid UTF-8 encoded unicode character
211  **/
212 gunichar
213 g_utf8_get_char (const gchar *p)
214 {
215   int i, mask = 0, len;
216   gunichar result;
217   unsigned char c = (unsigned char) *p;
218
219   UTF8_COMPUTE (c, mask, len);
220   if (len == -1)
221     return (gunichar)-1;
222   UTF8_GET (result, p, i, mask, len);
223
224   return result;
225 }
226
227 /**
228  * g_utf8_offset_to_pointer:
229  * @str: a UTF-8 encoded string
230  * @offset: a character offset within the string.
231  * 
232  * Converts from an integer character offset to a pointer to a position
233  * within the string.
234  * 
235  * Return value: the resulting pointer
236  **/
237 gchar *
238 g_utf8_offset_to_pointer  (const gchar *str,
239                            gint         offset)
240 {
241   const gchar *s = str;
242   while (offset--)
243     s = g_utf8_next_char (s);
244   
245   return (gchar *)s;
246 }
247
248 /**
249  * g_utf8_pointer_to_offset:
250  * @str: a UTF-8 encoded string
251  * @pos: a pointer to a position within @str
252  * 
253  * Converts from a pointer to position within a string to a integer
254  * character offset
255  * 
256  * Return value: the resulting character offset
257  **/
258 gint
259 g_utf8_pointer_to_offset (const gchar *str,
260                           const gchar *pos)
261 {
262   const gchar *s = str;
263   gint offset = 0;
264   
265   while (s < pos)
266     {
267       s = g_utf8_next_char (s);
268       offset++;
269     }
270
271   return offset;
272 }
273
274
275 gchar *
276 g_utf8_strncpy (gchar *dest, const gchar *src, size_t n)
277 {
278   const gchar *s = src;
279   while (n && *s)
280     {
281       s = g_utf8_next_char(s);
282       n--;
283     }
284   strncpy(dest, src, s - src);
285   dest[s - src] = 0;
286   return dest;
287 }
288
289 static gboolean
290 g_utf8_get_charset_internal (char **a)
291 {
292   char *charset = getenv("CHARSET");
293
294   if (charset && a && ! *a)
295     *a = charset;
296
297   if (charset && strstr (charset, "UTF-8"))
298       return TRUE;
299
300 #ifdef HAVE_CODESET
301   charset = nl_langinfo(CODESET);
302   if (charset)
303     {
304       if (a && ! *a)
305         *a = charset;
306       if (strcmp (charset, "UTF-8") == 0)
307         return TRUE;
308     }
309 #endif
310   
311 #if 0 /* #ifdef _NL_CTYPE_CODESET_NAME */
312   charset = nl_langinfo (_NL_CTYPE_CODESET_NAME);
313   if (charset)
314     {
315       if (a && ! *a)
316         *a = charset;
317       if (strcmp (charset, "UTF-8") == 0)
318         return TRUE;
319     }
320 #endif
321
322   if (a && ! *a) 
323     *a = "US-ASCII";
324   /* Assume this for compatibility at present.  */
325   return FALSE;
326 }
327
328 static int utf8_locale_cache = -1;
329 static char *utf8_charset_cache = NULL;
330
331 gboolean
332 g_get_charset (char **charset) 
333 {
334   if (utf8_locale_cache != -1)
335     {
336       if (charset)
337         *charset = utf8_charset_cache;
338       return utf8_locale_cache;
339     }
340   utf8_locale_cache = g_utf8_get_charset_internal (&utf8_charset_cache);
341   if (charset) 
342     *charset = utf8_charset_cache;
343   return utf8_locale_cache;
344 }
345
346 /* unicode_strchr */
347
348 /**
349  * g_unichar_to_utf8:
350  * @ch: a ISO10646 character code
351  * @out: output buffer, must have at least 6 bytes of space.
352  * 
353  * Convert a single character to utf8
354  * 
355  * Return value: number of bytes written
356  **/
357 int
358 g_unichar_to_utf8 (gunichar c, gchar *outbuf)
359 {
360   size_t len = 0;
361   int first;
362   int i;
363
364   if (c < 0x80)
365     {
366       first = 0;
367       len = 1;
368     }
369   else if (c < 0x800)
370     {
371       first = 0xc0;
372       len = 2;
373     }
374   else if (c < 0x10000)
375     {
376       first = 0xe0;
377       len = 3;
378     }
379    else if (c < 0x200000)
380     {
381       first = 0xf0;
382       len = 4;
383     }
384   else if (c < 0x4000000)
385     {
386       first = 0xf8;
387       len = 5;
388     }
389   else
390     {
391       first = 0xfc;
392       len = 6;
393     }
394
395   for (i = len - 1; i > 0; --i)
396     {
397       outbuf[i] = (c & 0x3f) | 0x80;
398       c >>= 6;
399     }
400   outbuf[0] = c | first;
401
402   return len;
403 }
404
405 /**
406  * g_utf8_strchr:
407  * @p: a nul-terminated utf-8 string
408  * @c: a iso-10646 character/
409  * 
410  * Find the leftmost occurence of the given iso-10646 character
411  * in a UTF-8 string.
412  * 
413  * Return value: NULL if the string does not contain the character, otherwise, a
414  *               a pointer to the start of the leftmost of the character in the string.
415  **/
416 gchar *
417 g_utf8_strchr (const char *p, gunichar c)
418 {
419   gchar ch[10];
420
421   gint len = g_unichar_to_utf8 (c, ch);
422   ch[len] = '\0';
423   
424   return strstr(p, ch);
425 }
426
427 #if 0
428 /**
429  * g_utf8_strrchr:
430  * @p: a nul-terminated utf-8 string
431  * @c: a iso-10646 character/
432  * 
433  * Find the rightmost occurence of the given iso-10646 character
434  * in a UTF-8 string.
435  * 
436  * Return value: NULL if the string does not contain the character, otherwise, a
437  *               a pointer to the start of the rightmost of the character in the string.
438  **/
439
440 /* This is ifdefed out atm as there is no strrstr function in libc.
441  */
442 gchar *
443 unicode_strrchr (const char *p, gunichar c)
444 {
445   gchar ch[10];
446
447   len = g_unichar_to_utf8 (c, ch);
448   ch[len] = '\0';
449   
450   return strrstr(p, ch);
451 }
452 #endif
453
454
455 /**
456  * g_utf8_to_ucs4:
457  * @str: a UTF-8 encoded strnig
458  * @len: the length of @
459  * 
460  * Convert a string from UTF-8 to a 32-bit fixed width
461  * representation as UCS-4.
462  * 
463  * Return value: a pointer to a newly allocated UCS-4 string.
464  *               This value must be freed with g_free()
465  **/
466 gunichar *
467 g_utf8_to_ucs4 (const char *str, int len)
468 {
469   gunichar *result;
470   gint n_chars, i;
471   const gchar *p;
472   
473   n_chars = g_utf8_strlen (str, len);
474   result = g_new (gunichar, n_chars);
475   
476   p = str;
477   for (i=0; i < n_chars; i++)
478     {
479       result[i] = g_utf8_get_char (p);
480       p = g_utf8_next_char (p);
481     }
482
483   return result;
484 }
485