OSDN Git Service

Hide my personal #warning reminders. Add __wcschrnul, rename strchrnul
[uclinux-h8/uClibc.git] / libc / misc / locale / locale.c
1 /*  Copyright (C) 2002     Manuel Novoa III
2  *
3  *  This library is free software; you can redistribute it and/or
4  *  modify it under the terms of the GNU Library General Public
5  *  License as published by the Free Software Foundation; either
6  *  version 2 of the License, or (at your option) any later version.
7  *
8  *  This library is distributed in the hope that it will be useful,
9  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
11  *  Library General Public License for more details.
12  *
13  *  You should have received a copy of the GNU Library General Public
14  *  License along with this library; if not, write to the Free
15  *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16  */
17
18 /*  TODO:
19  *  Implement the shared mmap code so non-mmu platforms can use this.
20  *  Add some basic collate functionality similar to what the previous
21  *    locale support had (8-bit codesets only).
22  */
23
24 #define _GNU_SOURCE
25 #include <locale.h>
26 #include <string.h>
27 #include <stdlib.h>
28 #include <stddef.h>
29 #include <limits.h>
30 #include <stdint.h>
31 #include <assert.h>
32
33 #ifndef __LOCALE_C_ONLY
34
35 #define CUR_LOCALE_SPEC                 (__global_locale.cur_locale)
36 #undef CODESET_LIST
37 #define CODESET_LIST                    (__locale_mmap->codeset_list)
38
39 /* TODO: Optional... See below. */
40 #define __LOCALE_STRICTER_SETLOCALE
41
42 #endif /* __LOCALE_C_ONLY */
43
44 /**********************************************************************/
45 #ifdef L_setlocale
46
47 #ifdef __LOCALE_C_ONLY
48
49 link_warning(setlocale,"the 'setlocale' function supports only C|POSIX locales")
50
51 static const char C_string[] = "C";
52
53 char *setlocale(int category, register const char *locale)
54 {
55         return ( (((unsigned int)(category)) <= LC_ALL)
56                          && ( (!locale)         /* Request for locale category string. */
57                                   || (!*locale) /* Implementation-defined default is C. */
58                                   || ((*locale == 'C') && !locale[1])
59                                   || (!strcmp(locale, "POSIX"))) )
60                 ? (char *) C_string             /* Always in C/POSIX locale. */
61                 : NULL;
62 }
63
64 #else  /* ---------------------------------------------- __LOCALE_C_ONLY */
65
66 #if !defined(NUM_LOCALES) || (NUM_LOCALES <= 1)
67 #error locales enabled, but not data other than for C locale!
68 #endif
69
70 static unsigned char setlocale_buf[LOCALE_STRING_SIZE];
71
72
73 #define LOCALE_NAMES                    (__locale_mmap->locale_names5)
74 #define LOCALES                                 (__locale_mmap->locales)
75 #define LOCALE_AT_MODIFIERS     (__locale_mmap->locale_at_modifiers)
76 #define CATEGORY_NAMES                  (__locale_mmap->lc_names)
77
78 static const char posix[] = "POSIX";
79
80 static int find_locale(int category, const char *p, unsigned char *new_locale)
81 {
82         int i;
83         const unsigned char *s;
84         uint16_t n;
85         unsigned char lang_cult, codeset;
86
87 #if defined(LOCALE_AT_MODIFIERS_LENGTH) && 1
88         /* Support standard locale handling for @-modifiers. */
89         char buf[18];   /* TODO: 7+{max codeset name length} */
90         const char *q;
91
92         if ((q = strchr(p,'@')) != NULL) {
93                 if ((((size_t)((q-p)-5)) > (sizeof(buf) - 5)) || (p[2] != '_')) {
94                         return 0;
95                 }
96                 /* locale name at least 5 chars long and 3rd char is '_' */
97                 s = LOCALE_AT_MODIFIERS;
98                 do {
99                         if (!strcmp(s+2, q+1)) {
100                                 break;
101                         }
102                         s += 2 + *s;            /* TODO - fix this throughout */
103                 } while (*s);
104                 if (!*s) {
105                         return 0;
106                 }
107                 memcpy(buf, p, q-p);
108                 buf[q-p] = 0;
109                 buf[2] = s[1];
110                 p = buf;
111         }
112 #endif
113
114         lang_cult = codeset = 0;        /* Assume C and default codeset.  */
115         if (((*p == 'C') && !p[1]) || !strcmp(p, posix)) {
116                 goto FIND_LOCALE;
117         }
118
119         if (p[5] == '.') {              /* Codeset specified in locale name? */
120                 /* TODO: maybe CODESET_LIST + *s ??? */
121                 /* 7bit is 1, UTF-8 is 2, 8-bit is >= 3 */
122                 codeset = 2;
123                 if (strcmp("UTF-8",p+6) != 0) {/* TODO - fix! */
124                         s = CODESET_LIST;
125                         do {
126                                 ++codeset;              /* Increment codeset first. */
127                                 if (!strcmp(CODESET_LIST+*s, p+6)) {
128                                         goto FIND_LANG_CULT;
129                                 }
130                         } while (*++s);
131                         return 0;                       /* No matching codeset! */
132                 }
133         }
134
135  FIND_LANG_CULT:                                /* Find language_culture number. */
136         s = LOCALE_NAMES;
137         do {                                            /* TODO -- do a binary search? */
138                 /* TODO -- fix gen_mmap!*/
139                 ++lang_cult;                    /* Increment first since C/POSIX is 0. */
140                 if (!strncmp(s,p,5)) { /* Found a matching locale name; */
141                         goto FIND_LOCALE;
142                 }
143                 s += 5;
144         } while (lang_cult < NUM_LOCALE_NAMES);
145         return 0;                                       /* No matching language_culture! */
146
147  FIND_LOCALE:                                   /* Find locale row matching name and codeset */
148         s = LOCALES;
149         n = 1;
150         do {                                            /* TODO -- do a binary search? */
151                 if ((lang_cult == *s) && ((codeset == s[1]) || (codeset == s[2]))) {
152                         i = ((category == LC_ALL) ? 0 : category);
153                         s = new_locale + 2*i;
154                         do {
155                                 /* Encode current locale row number. */
156                                 *((unsigned char *) ++s) = (n >> 8) | 0x80;
157                                 *((unsigned char *) ++s) = n & 0xff;
158                         } while (++i < category);
159
160                         return i;                       /* Return non-zero */
161                 }
162                 s += WIDTH_LOCALES;
163                 ++n;
164         } while (n <= NUM_LOCALES);     /* We started at 1!!! */
165
166         return 0;                                       /* Unsupported locale. */
167 }
168
169 char *setlocale(int category, const char *locale)
170 {
171         const unsigned char *p;
172         unsigned char *s;
173         int i;
174         unsigned lc_mask;
175         unsigned char new_locale[LOCALE_STRING_SIZE];
176
177         if (((unsigned int)(category)) > LC_ALL) {
178                 /* TODO - set errno?  SUSv3 doesn't say too. */
179                 return NULL;                    /* Illegal/unsupported category. */
180         }
181
182         lc_mask = 1 << category;
183         if (category == LC_ALL) {
184                 --lc_mask;
185         }
186
187         if (!locale) {                          /* Request for locale category string... */
188         DONE:
189                 strcpy(setlocale_buf, CUR_LOCALE_SPEC);
190 #ifdef __LOCALE_STRICTER_SETLOCALE
191                 /* The standard says you can only use the string returned to restore
192                  * the category (categories) requested.  This could be optional.
193                  * See below as well. */
194                 s = setlocale_buf + 1;
195                 lc_mask |= (1 << LC_ALL);
196                 do {
197                         if (!(lc_mask & 1)) {
198                                 /* Encode non-selected locale flag. */
199                                 s[1] = *s = 0xff;
200                         }
201                         s += 2;
202                 } while ((lc_mask >>= 1) > 1);
203 #endif /* __LOCALE_STRICTER_SETLOCALE */
204                 return (char *) setlocale_buf;
205         }
206
207         strcpy(new_locale, CUR_LOCALE_SPEC); /* Start with current. */
208
209         if (!*locale) {                         /* locale == "", so check environment. */
210                 i = ((category == LC_ALL) ? 0 : category);
211                 do {
212                         /* Note: SUSv3 doesn't define a fallback mechanism here.  So,
213                          * if LC_ALL is invalid, we do _not_ continue trying the other
214                          * environment vars. */
215                         if (!(p = getenv("LC_ALL"))) {
216                                 if (!(p = getenv(CATEGORY_NAMES + CATEGORY_NAMES[i]))) {
217                                         if (!(p = getenv("LANG"))) {
218                                                 p = posix;
219                                         }
220                                 }
221                         }
222
223                         /* The user set something... is it valid? */
224                         /* Note: Since we don't support user-supplied locales and
225                          * alternate paths, we don't need to worry about special
226                          * handling for suid/sgid apps. */
227                         if (!find_locale(i, p, new_locale)) {
228                                 return NULL;
229                         }
230                 } while (++i < category);
231         } else if (*locale == '#') { /* Previsouly returned value. */
232                 assert(strlen(locale) == LOCALE_STRING_SIZE - 1);
233
234                 i = ((category == LC_ALL) ? 0 : category);
235                 p = locale + 2*i;
236                 s = new_locale + 2*i;
237                 do {
238 #ifdef __LOCALE_STRICTER_SETLOCALE
239                         /* Only set categories that were selected in the previous
240                          * return value.  Could be optional.  See above as well.
241                          * NOTE: This still isn't quite right for non-LC_ALL
242                          * as it only checks the category selected to set. */
243                         if ((*p == 0xff) && (p[1] == 0xff)) {
244                                 return NULL;
245                         }
246 #endif /* __LOCALE_STRICTER_SETLOCALE */
247                         /* Note: Validate settings below. */
248                         *++s = *++p;
249                         *++s = *++p;
250                 } while (++i < category);
251         } else if (!find_locale(category, locale, new_locale)) {
252                 return NULL;
253         }
254
255
256         /* TODO: Ok, everything checks out, so install the new locale. */
257         _locale_set(new_locale);
258
259         /* Everything ok, so make a copy in setlocale_buf and return. */
260         goto DONE;
261 }
262
263 #endif /* __LOCALE_C_ONLY */
264
265 #endif
266 /**********************************************************************/
267 #ifdef L_localeconv
268
269 /* Note: We assume here that the compiler does the sane thing regarding
270  * placement of the fields in the struct.  If necessary, we could ensure
271  * this usings an array of offsets but at some size cost. */
272
273 #ifdef __LOCALE_C_ONLY
274
275 link_warning(localeconv,"the 'localeconv' function is hardwired for C/POSIX locale only")
276
277 static struct lconv the_lconv;
278
279 static const char decpt[] = ".";
280
281 struct lconv *localeconv(void)
282 {
283         register char *p = (char *)(&the_lconv);
284
285         *((char **)p) = (char *) decpt;
286         do {
287                 p += sizeof(char **);
288                 *((char **)p) = (char *) (decpt+1);
289         } while (p < (char *) &the_lconv.negative_sign);
290
291         p = (&the_lconv.int_frac_digits);
292         do {
293                 *p = CHAR_MAX;
294                 ++p;
295         } while (p <= &the_lconv.int_n_sign_posn);
296
297         return &the_lconv;
298 }
299
300 #else  /* __LOCALE_C_ONLY */
301
302 static struct lconv the_lconv;
303
304 struct lconv *localeconv(void)
305 {
306         register char *p = (char *) &the_lconv;
307         register char **q = (char **) &__global_locale.decimal_point;
308
309         do {
310                 *((char **)p) = *q;
311                 p += sizeof(char **);
312                 ++q;
313         } while (p < &the_lconv.int_frac_digits);
314
315         do {
316                 *p = **q;
317                 ++p;
318                 ++q;
319         } while (p <= &the_lconv.int_n_sign_posn);
320
321         return &the_lconv;
322 }
323
324 #endif /* __LOCALE_C_ONLY */
325
326 #endif
327 /**********************************************************************/
328 #ifdef L__locale_init
329
330 #ifndef __LOCALE_C_ONLY
331
332 #define C_LOCALE_SELECTOR "\x23\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01\x80\x01"
333 #define LOCALE_INIT_FAILED "locale init failed!\n"
334
335 #define CUR_LOCALE_SPEC                 (__global_locale.cur_locale)
336
337 __locale_t __global_locale;
338
339 void _locale_init(void)
340 {
341         /* TODO: mmap the locale file  */
342
343         /* TODO - ??? */
344         memset(CUR_LOCALE_SPEC, 0, LOCALE_STRING_SIZE);
345         CUR_LOCALE_SPEC[0] = '#';
346
347         memcpy(__global_locale.category_item_count,
348                    __locale_mmap->lc_common_item_offsets_LEN,
349                    LC_ALL);
350
351         __global_locale.category_offsets[0] = offsetof(__locale_t, codeset);
352         __global_locale.category_offsets[1] = offsetof(__locale_t, decimal_point);
353         __global_locale.category_offsets[2] = offsetof(__locale_t, int_curr_symbol);
354         __global_locale.category_offsets[3] = offsetof(__locale_t, abday_1);
355 /*      __global_locale.category_offsets[4] = offsetof(__locale_t, collate???); */
356         __global_locale.category_offsets[5] = offsetof(__locale_t, yesexpr);
357
358 #ifdef __CTYPE_HAS_8_BIT_LOCALES
359         __global_locale.tbl8ctype
360                 = (const unsigned char *) &__locale_mmap->tbl8ctype;
361     __global_locale.tbl8uplow
362                 = (const unsigned char *) &__locale_mmap->tbl8uplow;
363 #ifdef __WCHAR_ENABLED
364         __global_locale.tbl8c2wc
365                 = (const uint16_t *) &__locale_mmap->tbl8c2wc;
366         __global_locale.tbl8wc2c
367                 = (const unsigned char *) &__locale_mmap->tbl8wc2c;
368         /* translit  */
369 #endif /* __WCHAR_ENABLED */
370 #endif /* __CTYPE_HAS_8_BIT_LOCALES */
371 #ifdef __WCHAR_ENABLED
372         __global_locale.tblwctype
373                 = (const unsigned char *) &__locale_mmap->tblwctype;
374         __global_locale.tblwuplow
375                 = (const unsigned char *) &__locale_mmap->tblwuplow;
376         __global_locale.tblwuplow_diff
377                 = (const uint16_t *) &__locale_mmap->tblwuplow_diff;
378         __global_locale.tblwcomb
379                 = (const unsigned char *) &__locale_mmap->tblwcomb;
380         /* width?? */
381 #endif /* __WCHAR_ENABLED */
382
383         _locale_set(C_LOCALE_SELECTOR);
384 }
385
386 static const char ascii[] = "ASCII";
387 static const char utf8[] = "UTF-8";
388
389 void _locale_set(const unsigned char *p)
390 {
391         const char **x;
392         unsigned char *s = CUR_LOCALE_SPEC + 1;
393         const size_t *stp;
394         const unsigned char *r;
395         const uint16_t *io;
396         const uint16_t *ii;
397         const unsigned char *d;
398         int row;                                        /* locale row */
399         int crow;                                       /* category row */
400         int len;
401         int c;
402         int i = 0;
403
404         ++p;
405         do {
406                 if ((*p != *s) || (p[1] != s[1])) {
407                         row  = (((int)(*p & 0x7f)) << 8) + p[1] - 1;
408 #ifndef NDEBUG
409                         assert(row < NUM_LOCALES);
410 #endif
411                         *s = *p;
412                         s[1] = p[1];
413
414                         if (i == LC_CTYPE) {
415                                 c = __locale_mmap->locales[ WIDTH_LOCALES * row + 2 ]; /* codeset */
416                                 if (c <= 2) {
417                                         if (c == 2) {
418                                                 __global_locale.codeset = utf8;
419                                                 __global_locale.encoding = __ctype_encoding_utf8;
420                                                 /* TODO - fix for bcc */
421                                                 __global_locale.mb_cur_max = 6;
422                                         } else {
423                                                 assert(c==1);
424                                                 __global_locale.codeset = ascii;
425                                                 __global_locale.encoding = __ctype_encoding_7_bit;
426                                                 __global_locale.mb_cur_max = 1;
427                                         }
428                                 } else {
429                                         const codeset_8_bit_t *c8b;
430                                         r = CODESET_LIST;
431                                         __global_locale.codeset = r + r[c -= 3];
432                                         __global_locale.encoding = __ctype_encoding_8_bit;
433 #ifdef __UCLIBC_MJN3_ONLY__
434 #warning REMINDER: update 8 bit mb_cur_max when trasnlit implemented!
435 #endif
436                                         /* TODO - update when translit implemented! */
437                                         __global_locale.mb_cur_max = 1;
438                                         c8b = __locale_mmap->codeset_8_bit + c;
439 #ifdef __CTYPE_HAS_8_BIT_LOCALES
440                                         __global_locale.idx8ctype = c8b->idx8ctype;
441                                         __global_locale.idx8uplow = c8b->idx8uplow;
442 #ifdef __WCHAR_ENABLED
443                                         __global_locale.idx8c2wc = c8b->idx8c2wc;
444                                         __global_locale.idx8wc2c = c8b->idx8wc2c;
445                                         /* translit  */
446 #endif /* __WCHAR_ENABLED */
447 #endif /* __CTYPE_HAS_8_BIT_LOCALES */
448                                 }
449
450                         } else if ((len = __locale_mmap->lc_common_item_offsets_LEN[i]) != 0) {
451                                 crow = __locale_mmap->locales[ WIDTH_LOCALES * row + 3 + i ]
452                                         * len;
453                                 x = (const char **)(((char *) &__global_locale)
454                                                                         + __global_locale.category_offsets[i]);
455                                 stp = __locale_mmap->lc_common_tbl_offsets + 4*i;
456                                 r = (const unsigned char *)( ((char *)__locale_mmap) + *stp );
457                                 io = (const uint16_t *)( ((char *)__locale_mmap) + *++stp );
458                                 ii = (const uint16_t *)( ((char *)__locale_mmap) + *++stp );
459                                 d = (const unsigned char *)( ((char *)__locale_mmap) + *++stp );
460                                 for (c=0 ; c < len ; c++) {
461                                         *(x + c) = d + ii[ r[crow + c] + io[c] ];
462                                 }
463                         }
464
465                 }
466                 ++i;
467                 p += 2;
468                 s += 2;
469         } while (i < LC_ALL);
470 }
471
472 #endif /* __LOCALE_C_ONLY */
473
474 #endif
475 /**********************************************************************/
476 #ifdef L_nl_langinfo
477
478 #include <langinfo.h>
479 #include <nl_types.h>
480
481 #ifdef __LOCALE_C_ONLY
482
483 /* We need to index 300 bytes of data, so you might initially think we
484  * need to store the offsets in shorts.  But since the offset of the
485  * 64th item is 231, we'll store "offset - 64" for all items >= 64
486  * and always calculate the data offset as "offset[i] + (i & 64)".
487  * This allows us to pack the data offsets in an unsigned char while
488  * also avoiding an "if".
489  *
490  * Note: Category order is assumed to be:
491  *   ctype, numeric, monetary, time, collate, messages, all
492  */
493
494 #define C_LC_ALL 6
495
496 /* Combine the data to avoid size penalty for seperate char arrays when
497  * compiler aligns objects.  The original code is left in as documentation. */
498 #define cat_start nl_data
499 #define C_locale_data (nl_data + C_LC_ALL + 1 + 78)
500
501 static const unsigned char nl_data[C_LC_ALL + 1 + 78 + 300] = {
502 /*  static const unsigned char cat_start[C_LC_ALL + 1] = { */
503         '\x00', '\x01', '\x04', '\x1a', '\x4c', '\x4c', '\x4e', 
504 /*  }; */
505 /*  static const unsigned char item_offset[78] = { */
506         '\x00', '\x06', '\x07', '\x07', '\x07', '\x07', '\x07', '\x07', 
507         '\x07', '\x07', '\x07', '\x08', '\x08', '\x08', '\x08', '\x08', 
508         '\x08', '\x08', '\x08', '\x08', '\x08', '\x08', '\x08', '\x08', 
509         '\x08', '\x0a', '\x0c', '\x10', '\x14', '\x18', '\x1c', '\x20', 
510         '\x24', '\x28', '\x2f', '\x36', '\x3e', '\x48', '\x51', '\x58', 
511         '\x61', '\x65', '\x69', '\x6d', '\x71', '\x75', '\x79', '\x7d', 
512         '\x81', '\x85', '\x89', '\x8d', '\x91', '\x99', '\xa2', '\xa8', 
513         '\xae', '\xb2', '\xb7', '\xbc', '\xc3', '\xcd', '\xd5', '\xde', 
514         '\xa7', '\xaa', '\xad', '\xc2', '\xcb', '\xd4', '\xdf', '\xdf', 
515         '\xdf', '\xdf', '\xdf', '\xdf', '\xe0', '\xe6', 
516 /*  }; */
517 /*  static const unsigned char C_locale_data[300] = { */
518            'A',    'S',    'C',    'I',    'I', '\x00',    '.', '\x00', 
519         '\x7f', '\x00',    '-', '\x00',    'S',    'u',    'n', '\x00', 
520            'M',    'o',    'n', '\x00',    'T',    'u',    'e', '\x00', 
521            'W',    'e',    'd', '\x00',    'T',    'h',    'u', '\x00', 
522            'F',    'r',    'i', '\x00',    'S',    'a',    't', '\x00', 
523            'S',    'u',    'n',    'd',    'a',    'y', '\x00',    'M', 
524            'o',    'n',    'd',    'a',    'y', '\x00',    'T',    'u', 
525            'e',    's',    'd',    'a',    'y', '\x00',    'W',    'e', 
526            'd',    'n',    'e',    's',    'd',    'a',    'y', '\x00', 
527            'T',    'h',    'u',    'r',    's',    'd',    'a',    'y', 
528         '\x00',    'F',    'r',    'i',    'd',    'a',    'y', '\x00', 
529            'S',    'a',    't',    'u',    'r',    'd',    'a',    'y', 
530         '\x00',    'J',    'a',    'n', '\x00',    'F',    'e',    'b', 
531         '\x00',    'M',    'a',    'r', '\x00',    'A',    'p',    'r', 
532         '\x00',    'M',    'a',    'y', '\x00',    'J',    'u',    'n', 
533         '\x00',    'J',    'u',    'l', '\x00',    'A',    'u',    'g', 
534         '\x00',    'S',    'e',    'p', '\x00',    'O',    'c',    't', 
535         '\x00',    'N',    'o',    'v', '\x00',    'D',    'e',    'c', 
536         '\x00',    'J',    'a',    'n',    'u',    'a',    'r',    'y', 
537         '\x00',    'F',    'e',    'b',    'r',    'u',    'a',    'r', 
538            'y', '\x00',    'M',    'a',    'r',    'c',    'h', '\x00', 
539            'A',    'p',    'r',    'i',    'l', '\x00',    'M',    'a', 
540            'y', '\x00',    'J',    'u',    'n',    'e', '\x00',    'J', 
541            'u',    'l',    'y', '\x00',    'A',    'u',    'g',    'u', 
542            's',    't', '\x00',    'S',    'e',    'p',    't',    'e', 
543            'm',    'b',    'e',    'r', '\x00',    'O',    'c',    't', 
544            'o',    'b',    'e',    'r', '\x00',    'N',    'o',    'v', 
545            'e',    'm',    'b',    'e',    'r', '\x00',    'D',    'e', 
546            'c',    'e',    'm',    'b',    'e',    'r', '\x00',    'A', 
547            'M', '\x00',    'P',    'M', '\x00',    '%',    'a',    ' ', 
548            '%',    'b',    ' ',    '%',    'e',    ' ',    '%',    'H', 
549            ':',    '%',    'M',    ':',    '%',    'S',    ' ',    '%', 
550            'Y', '\x00',    '%',    'm',    '/',    '%',    'd',    '/', 
551            '%',    'y', '\x00',    '%',    'H',    ':',    '%',    'M', 
552            ':',    '%',    'S', '\x00',    '%',    'I',    ':',    '%', 
553            'M',    ':',    '%',    'S',    ' ',    '%',    'p', '\x00', 
554            '^',    '[',    'y',    'Y',    ']', '\x00',    '^',    '[', 
555            'n',    'N',    ']', '\x00', 
556 };
557
558 char *nl_langinfo(nl_item item)
559 {
560         unsigned int c;
561         unsigned int i;
562
563         if ((c = _NL_ITEM_CATEGORY(item)) < C_LC_ALL) {
564                 if ((i = cat_start[c] + _NL_ITEM_INDEX(item)) < cat_start[c+1]) {
565 /*                      return (char *) C_locale_data + item_offset[i] + (i & 64); */
566                         return (char *) C_locale_data + nl_data[C_LC_ALL+1+i] + (i & 64);
567                 }
568         }
569         return (char *) cat_start;      /* Conveniently, this is the empty string. */
570 }
571
572 #else  /* __LOCALE_C_ONLY */
573
574 static const char empty[] = "";
575
576 char *nl_langinfo(nl_item item)
577 {
578         unsigned int c = _NL_ITEM_CATEGORY(item);
579         unsigned int i = _NL_ITEM_INDEX(item);
580
581         if ((c < LC_ALL) && (i < __global_locale.category_item_count[c])) {
582                 return ((char **)(((char *) &__global_locale)
583                                                   + __global_locale.category_offsets[c]))[i];
584
585         }
586         return (char *) empty;
587 }
588
589 #endif /* __LOCALE_C_ONLY */
590
591 #endif
592 /**********************************************************************/