Make PHP 5 happy.
[elinks.git] / src / intl / gettext / dcigettext.c
blobd92b861143eb67a231e26e6aed0aef8ece13de6d
1 /* Implementation of the internal dcigettext function.
2 Copyright (C) 1995-1999, 2000, 2001 Free Software Foundation, Inc.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2, or (at your option)
7 any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software Foundation,
16 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
18 /* Tell glibc's <string.h> to provide a prototype for mempcpy().
19 This must come before <config.h> because <config.h> may include
20 <features.h>, and once <features.h> has been included, it's too late. */
21 #ifndef _GNU_SOURCE
22 #define _GNU_SOURCE 1
23 #endif
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
29 #include <sys/types.h>
31 #if defined HAVE_ALLOCA_H
32 #include <alloca.h>
33 #else
34 #ifdef _AIX
35 #pragma alloca
36 #else
37 #ifndef HAVE_ALLOCA
38 void *alloca(size_t size);
39 #endif
40 #endif
41 #endif
43 #include <errno.h>
44 #ifndef errno
45 extern int errno;
46 #endif
48 #include <stddef.h>
49 #include <stdlib.h>
51 #include <string.h>
53 #if defined HAVE_UNISTD_H
54 #include <unistd.h>
55 #endif
57 #if defined HAVE_SYS_PARAM_H
58 #include <sys/param.h>
59 #endif
61 #ifdef HAVE_MALLOC_H
62 #include <malloc.h> /* Needed for alloca() on MingW/Win32 */
63 #endif
65 #include "elinks.h"
67 #include "intl/gettext/gettextP.h"
68 #include "intl/gettext/libintl.h"
69 #include "intl/gettext/hash-string.h"
70 #include "util/string.h"
72 /* Amount to increase buffer size by in each try. */
73 #define PATH_INCR 32
75 /* The following is from pathmax.h. */
76 /* Non-POSIX BSD systems might have gcc's limits.h, which doesn't define
77 PATH_MAX but might cause redefinition warnings when sys/param.h is
78 later included (as on MORE/BSD 4.3). */
79 #if defined _POSIX_VERSION || defined HAVE_LIMITS_H
80 #include <limits.h>
81 #endif
83 #ifndef _POSIX_PATH_MAX
84 #define _POSIX_PATH_MAX 255
85 #endif
87 #if !defined PATH_MAX && defined _PC_PATH_MAX
88 #define PATH_MAX (pathconf ("/", _PC_PATH_MAX) < 1 ? 1024 : pathconf ("/", _PC_PATH_MAX))
89 #endif
91 /* Don't include sys/param.h if it already has been. */
92 #if defined HAVE_SYS_PARAM_H && !defined PATH_MAX && !defined MAXPATHLEN
93 #include <sys/param.h>
94 #endif
96 #if !defined PATH_MAX && defined MAXPATHLEN
97 #define PATH_MAX MAXPATHLEN
98 #endif
100 #ifndef PATH_MAX
101 #define PATH_MAX _POSIX_PATH_MAX
102 #endif
104 /* Pathname support.
105 IS_ABSOLUTE_PATH(P) tests whether P is an absolute path. If it is not,
106 it may be concatenated to a directory pathname.
107 IS_PATH_WITH_DIR(P) tests whether P contains a directory specification.
109 #if defined _WIN32 || defined __WIN32__ || defined __EMX__ || defined __DJGPP__
110 /* Win32, OS/2, DOS */
111 #define HAS_DEVICE(P) (isasciialpha((P)[0]) && (P)[1] == ':')
112 #define IS_ABSOLUTE_PATH(P) (dir_sep((P)[0]) || HAS_DEVICE (P))
113 #define IS_PATH_WITH_DIR(P) (strchr (P, '/') || strchr (P, '\\') || HAS_DEVICE (P))
114 #else
115 /* Unix */
116 #define IS_ABSOLUTE_PATH(P) dir_sep((P)[0])
117 #define IS_PATH_WITH_DIR(P) strchr (P, '/')
118 #endif
120 /* XPG3 defines the result of `setlocale (category, NULL)' as:
121 ``Directs `setlocale()' to query `category' and return the current
122 setting of `local'.''
123 However it does not specify the exact format. Neither do SUSV2 and
124 ISO C 99. So we can use this feature only on selected systems (e.g.
125 those using GNU C Library). */
126 #if (defined __GNU_LIBRARY__ && __GNU_LIBRARY__ >= 2)
127 #define HAVE_LOCALE_NULL
128 #endif
130 /* This is the type used for the search tree where known translations
131 are stored. */
132 struct known_translation_t {
133 /* Domain in which to search. */
134 unsigned char *domainname;
136 /* The category. */
137 int category;
139 /* State of the catalog counter at the point the string was found. */
140 int counter;
142 /* Catalog where the string was found. */
143 struct loaded_l10nfile *domain;
145 /* And finally the translation. */
146 const unsigned char *translation;
147 size_t translation_length;
149 /* Pointer to the string in question. */
150 unsigned char msgid[1];
153 /* Root of the search tree with known translations. We can use this
154 only if the system provides the `tsearch' function family. */
155 #if defined HAVE_TSEARCH
156 #include <search.h>
158 static void *root;
160 /* Function to compare two entries in the table of known translations. */
161 static int transcmp(const void *p1, const void *p2);
164 static int
165 transcmp(const void *p1, const void *p2)
167 const struct known_translation_t *s1;
168 const struct known_translation_t *s2;
169 int result;
171 s1 = (const struct known_translation_t *) p1;
172 s2 = (const struct known_translation_t *) p2;
174 result = strcmp(s1->msgid, s2->msgid);
175 if (result == 0) {
176 result = strcmp(s1->domainname, s2->domainname);
177 if (result == 0)
178 /* We compare the category last (though this is the cheapest
179 operation) since it is hopefully always the same (namely
180 LC_MESSAGES). */
181 result = s1->category - s2->category;
184 return result;
186 #endif
188 /* Name of the default domain used for gettext(3) prior any call to
189 textdomain(3). The default value for this is "messages". */
190 const unsigned char _nl_default_default_domain__[] = "messages";
192 /* Value used as the default domain for gettext(3). */
193 const unsigned char *_nl_current_default_domain__ = _nl_default_default_domain__;
195 /* Contains the default location of the message catalogs. */
196 const unsigned char _nl_default_dirname__[] = LOCALEDIR;
198 /* Contains application-specific LANGUAGE variation, taking precedence to the
199 * $LANGUAGE environment variable. */
200 unsigned char *LANGUAGE = NULL;
202 /* List with bindings of specific domains created by bindtextdomain()
203 calls. */
204 struct binding *_nl_domain_bindings__;
206 /* Prototypes for local functions. */
207 static unsigned char *plural_lookup(struct loaded_l10nfile * domain,
208 unsigned long int n,
209 const unsigned char *translation,
210 size_t translation_len);
211 static unsigned long int plural_eval(struct expression * pexp,
212 unsigned long int n);
213 static const unsigned char *category_to_name(int category);
214 static const unsigned char *guess_category_value(int category,
215 const unsigned char *categoryname);
217 /* For those loosing systems which don't have `alloca' we have to add
218 some additional code emulating it. */
219 #ifdef HAVE_ALLOCA
220 /* Nothing has to be done. */
221 #define ADD_BLOCK(list, address) /* nothing */
222 #define FREE_BLOCKS(list) /* nothing */
223 #else
224 struct block_list {
225 void *address;
226 struct block_list *next;
228 #define ADD_BLOCK(list, addr) \
229 do { \
230 struct block_list *newp = (struct block_list *) malloc(sizeof(*newp)); \
231 /* If we cannot get a free block we cannot add the new element to \
232 the list. */ \
233 if (newp != NULL) { \
234 newp->address = (addr); \
235 newp->next = (list); \
236 (list) = newp; \
238 } while (0)
239 #define FREE_BLOCKS(list) \
240 do { \
241 while (list != NULL) { \
242 struct block_list *old = list; \
243 list = list->next; \
244 free (old->address); \
245 free (old); \
247 } while (0)
248 #undef alloca
249 #define alloca(size) (malloc (size))
250 #endif /* have alloca */
252 typedef unsigned char transmem_block_t;
254 /* Checking whether the binaries runs SUID must be done and glibc provides
255 easier methods therefore we make a difference here. */
256 #ifndef HAVE_GETUID
257 #define getuid() 0
258 #endif
260 #ifndef HAVE_GETGID
261 #define getgid() 0
262 #endif
264 #ifndef HAVE_GETEUID
265 #define geteuid() getuid()
266 #endif
268 #ifndef HAVE_GETEGID
269 #define getegid() getgid()
270 #endif
272 static int enable_secure;
274 #define ENABLE_SECURE (enable_secure == 1)
275 #define DETERMINE_SECURE \
276 if (enable_secure == 0) \
278 if (getuid () != geteuid () || getgid () != getegid ()) \
279 enable_secure = 1; \
280 else \
281 enable_secure = -1; \
284 /* Look up MSGID in the DOMAINNAME message catalog for the current
285 CATEGORY locale and, if PLURAL is nonzero, search over string
286 depending on the plural form determined by N. */
287 unsigned char *
288 dcigettext__(const unsigned char *domainname, const unsigned char *msgid1, const unsigned char *msgid2,
289 int plural, unsigned long int n, int category)
291 #ifndef HAVE_ALLOCA
292 struct block_list *block_list = NULL;
293 #endif
294 struct loaded_l10nfile *domain;
295 struct binding *binding;
296 const unsigned char *categoryname;
297 const unsigned char *categoryvalue;
298 unsigned char *dirname, *xdomainname;
299 unsigned char *single_locale;
300 unsigned char *retval;
301 size_t retlen;
302 int saved_errno;
304 #if defined HAVE_TSEARCH
305 struct known_translation_t *search;
306 struct known_translation_t **foundp = NULL;
307 size_t msgid_len;
308 #endif
309 size_t domainname_len;
311 /* If no real MSGID is given return NULL. */
312 if (msgid1 == NULL)
313 return NULL;
315 /* If DOMAINNAME is NULL, we are interested in the default domain. If
316 CATEGORY is not LC_MESSAGES this might not make much sense but the
317 definition left this undefined. */
318 if (domainname == NULL)
319 domainname = _nl_current_default_domain__;
321 #if defined HAVE_TSEARCH
322 msgid_len = strlen(msgid1) + 1;
324 /* Try to find the translation among those which we found at
325 some time. */
326 search = (struct known_translation_t *)
327 alloca(offsetof(struct known_translation_t, msgid) + msgid_len);
329 memcpy(search->msgid, msgid1, msgid_len);
330 search->domainname = (unsigned char *) domainname;
331 search->category = category;
333 foundp = (struct known_translation_t **) tfind(search, &root, transcmp);
334 if (foundp != NULL && (*foundp)->counter == _nl_msg_cat_cntr) {
335 /* Now deal with plural. */
336 if (plural)
337 retval = plural_lookup((*foundp)->domain, n,
338 (*foundp)->translation,
339 (*foundp)->translation_length);
340 else
341 retval = (unsigned char *) (*foundp)->translation;
343 return retval;
345 #endif
347 /* Preserve the `errno' value. */
348 saved_errno = errno;
350 /* See whether this is a SUID binary or not. */
351 DETERMINE_SECURE;
353 /* First find matching binding. */
354 for (binding = _nl_domain_bindings__; binding != NULL;
355 binding = binding->next) {
356 int compare = strcmp(domainname, binding->domainname);
358 if (compare == 0)
359 /* We found it! */
360 break;
361 if (compare < 0) {
362 /* It is not in the list. */
363 binding = NULL;
364 break;
368 if (binding == NULL)
369 dirname = (unsigned char *) _nl_default_dirname__;
370 else if (IS_ABSOLUTE_PATH(binding->dirname))
371 dirname = binding->dirname;
372 else {
373 /* We have a relative path. Make it absolute now. */
374 size_t dirname_len = strlen(binding->dirname) + 1;
375 size_t path_max;
376 unsigned char *ret;
378 path_max = (unsigned int) PATH_MAX;
379 path_max += 2; /* The getcwd docs say to do this. */
381 for (;;) {
382 dirname = (unsigned char *) alloca(path_max + dirname_len);
383 ADD_BLOCK(block_list, dirname);
385 errno = 0;
386 ret = getcwd(dirname, path_max);
387 if (ret != NULL || errno != ERANGE)
388 break;
390 path_max += path_max / 2;
391 path_max += PATH_INCR;
394 if (ret == NULL) {
395 /* We cannot get the current working directory. Don't signal an
396 error but simply return the default string. */
397 FREE_BLOCKS(block_list);
398 errno = saved_errno;
399 return (plural == 0 ? (unsigned char *) msgid1
400 /* Use the Germanic plural rule. */
401 : n == 1 ? (unsigned char *) msgid1 : (unsigned char *) msgid2);
404 stpcpy(stpcpy(strchr(dirname, '\0'), "/"), binding->dirname);
407 /* Now determine the symbolic name of CATEGORY and its value. */
408 categoryname = category_to_name(category);
409 categoryvalue = guess_category_value(category, categoryname);
411 domainname_len = strlen(domainname);
412 xdomainname = (unsigned char *) alloca(strlen(categoryname)
413 + domainname_len + 5);
414 ADD_BLOCK(block_list, xdomainname);
416 stpcpy(mempcpy(stpcpy(stpcpy(xdomainname, categoryname), "/"),
417 domainname, domainname_len), ".mo");
419 /* Creating working area. */
420 single_locale = (unsigned char *) alloca(strlen(categoryvalue) + 1);
421 ADD_BLOCK(block_list, single_locale);
423 /* Search for the given string. This is a loop because we perhaps
424 got an ordered list of languages to consider for the translation. */
425 while (1) {
426 /* Make CATEGORYVALUE point to the next element of the list. */
427 while (categoryvalue[0] != '\0' && categoryvalue[0] == ':')
428 ++categoryvalue;
429 if (categoryvalue[0] == '\0') {
430 /* The whole contents of CATEGORYVALUE has been searched but
431 no valid entry has been found. We solve this situation
432 by implicitly appending a "C" entry, i.e. no translation
433 will take place. */
434 single_locale[0] = 'C';
435 single_locale[1] = '\0';
436 } else {
437 unsigned char *cp = single_locale;
439 while (categoryvalue[0] != '\0'
440 && categoryvalue[0] != ':')
441 *cp++ = *categoryvalue++;
442 *cp = '\0';
444 /* When this is a SUID binary we must not allow accessing files
445 outside the dedicated directories. */
446 if (ENABLE_SECURE && IS_PATH_WITH_DIR(single_locale))
447 /* Ingore this entry. */
448 continue;
451 /* If the current locale value is C (or POSIX) we don't load a
452 domain. Return the MSGID. */
453 if (strcmp(single_locale, "C") == 0
454 || strcmp(single_locale, "POSIX") == 0) {
455 FREE_BLOCKS(block_list);
456 errno = saved_errno;
457 return (plural == 0 ? (unsigned char *) msgid1
458 /* Use the Germanic plural rule. */
459 : n == 1 ? (unsigned char *) msgid1 : (unsigned char *) msgid2);
462 /* Find structure describing the message catalog matching the
463 DOMAINNAME and CATEGORY. */
464 domain = _nl_find_domain(dirname, single_locale, xdomainname,
465 binding);
467 if (domain != NULL) {
468 retval = _nl_find_msg(domain, binding, msgid1, &retlen);
470 if (retval == NULL) {
471 int cnt;
473 for (cnt = 0; domain->successor[cnt] != NULL;
474 ++cnt) {
475 retval = _nl_find_msg(domain->
476 successor[cnt],
477 binding, msgid1,
478 &retlen);
480 if (retval != NULL) {
481 domain = domain->successor[cnt];
482 break;
487 if (retval != NULL) {
488 /* Found the translation of MSGID1 in domain DOMAIN:
489 starting at RETVAL, RETLEN bytes. */
490 FREE_BLOCKS(block_list);
491 errno = saved_errno;
492 #if defined HAVE_TSEARCH
493 if (foundp == NULL) {
494 /* Create a new entry and add it to the search tree. */
495 struct known_translation_t *newp;
497 newp = (struct known_translation_t *)
498 malloc(offsetof
499 (struct
500 known_translation_t,
501 msgid)
502 + msgid_len +
503 domainname_len + 1);
504 if (newp != NULL) {
505 newp->domainname =
506 mempcpy(newp->msgid,
507 msgid1,
508 msgid_len);
509 memcpy(newp->domainname,
510 domainname,
511 domainname_len + 1);
512 newp->category = category;
513 newp->counter =
514 _nl_msg_cat_cntr;
515 newp->domain = domain;
516 newp->translation = retval;
517 newp->translation_length =
518 retlen;
520 /* Insert the entry in the search tree. */
521 foundp = (struct
522 known_translation_t
524 tsearch(newp, &root,
525 transcmp);
526 if (foundp == NULL
527 || *foundp != newp)
528 /* The insert failed. */
529 free(newp);
531 } else {
532 /* We can update the existing entry. */
533 (*foundp)->counter = _nl_msg_cat_cntr;
534 (*foundp)->domain = domain;
535 (*foundp)->translation = retval;
536 (*foundp)->translation_length = retlen;
538 #endif
539 /* Now deal with plural. */
540 if (plural)
541 retval = plural_lookup(domain, n,
542 retval, retlen);
544 return retval;
548 /* NOTREACHED */
551 unsigned char *
552 _nl_find_msg(struct loaded_l10nfile *domain_file,
553 struct binding *domainbinding,
554 const unsigned char *msgid, size_t *lengthp)
556 struct loaded_domain *domain;
557 size_t act;
558 unsigned char *result;
559 size_t resultlen;
561 if (domain_file->decided == 0)
562 _nl_load_domain(domain_file, domainbinding);
564 if (domain_file->data == NULL)
565 return NULL;
567 domain = (struct loaded_domain *) domain_file->data;
569 /* Locate the MSGID and its translation. */
570 if (domain->hash_size > 2 && domain->hash_tab != NULL) {
571 /* Use the hashing table. */
572 nls_uint32 len = strlen(msgid);
573 nls_uint32 hash_val = hash_string(msgid);
574 nls_uint32 idx = hash_val % domain->hash_size;
575 nls_uint32 incr = 1 + (hash_val % (domain->hash_size - 2));
577 while (1) {
578 nls_uint32 nstr =
579 W(domain->must_swap, domain->hash_tab[idx]);
581 if (nstr == 0)
582 /* Hash table entry is empty. */
583 return NULL;
585 /* Compare msgid with the original string at index nstr-1.
586 We compare the lengths with >=, not ==, because plural entries
587 are represented by strings with an embedded NUL. */
588 if (W
589 (domain->must_swap,
590 domain->orig_tab[nstr - 1].length) >= len
592 (strcmp
593 (msgid,
594 domain->data + W(domain->must_swap,
595 domain->orig_tab[nstr -
596 1].offset))
597 == 0)) {
598 act = nstr - 1;
599 goto found;
602 if (idx >= domain->hash_size - incr)
603 idx -= domain->hash_size - incr;
604 else
605 idx += incr;
607 /* NOTREACHED */
608 } else {
609 /* Try the default method: binary search in the sorted array of
610 messages. */
611 size_t top, bottom;
613 bottom = 0;
614 top = domain->nstrings;
615 while (bottom < top) {
616 int cmp_val;
618 act = (bottom + top) / 2;
619 cmp_val = strcmp(msgid, (domain->data
620 + W(domain->must_swap,
621 domain->orig_tab[act].
622 offset)));
623 if (cmp_val < 0)
624 top = act;
625 else if (cmp_val > 0)
626 bottom = act + 1;
627 else
628 goto found;
630 /* No translation was found. */
631 return NULL;
634 found:
635 /* The translation was found at index ACT. If we have to convert the
636 string to use a different character set, this is the time. */
637 result = ((unsigned char *) domain->data
638 + W(domain->must_swap, domain->trans_tab[act].offset));
639 resultlen = W(domain->must_swap, domain->trans_tab[act].length) + 1;
641 #if HAVE_ICONV
642 if (domain->codeset_cntr
643 != (domainbinding != NULL ? domainbinding->codeset_cntr : 0)) {
644 /* The domain's codeset has changed through bind_textdomain_codeset()
645 since the message catalog was initialized or last accessed. We
646 have to reinitialize the converter. */
647 _nl_free_domain_conv(domain);
648 _nl_init_domain_conv(domain_file, domain, domainbinding);
651 if (domain->conv != (iconv_t) - 1) {
652 /* We are supposed to do a conversion. First allocate an
653 appropriate table with the same structure as the table
654 of translations in the file, where we can put the pointers
655 to the converted strings in.
656 There is a slight complication with plural entries. They
657 are represented by consecutive NUL terminated strings. We
658 handle this case by converting RESULTLEN bytes, including
659 NULs. */
661 if (domain->conv_tab == NULL
662 && ((domain->conv_tab = (unsigned char **) calloc(domain->nstrings,
663 sizeof(unsigned char *)))
664 == NULL))
665 /* Mark that we didn't succeed allocating a table. */
666 domain->conv_tab = (unsigned char **) -1;
668 if (domain->conv_tab == (unsigned char **) -1)
669 /* Nothing we can do, no more memory. */
670 goto converted;
672 if (domain->conv_tab[act] == NULL) {
673 /* We haven't used this string so far, so it is not
674 translated yet. Do this now. */
675 /* We use a bit more efficient memory handling.
676 We allocate always larger blocks which get used over
677 time. This is faster than many small allocations. */
678 #define INITIAL_BLOCK_SIZE 4080
679 static unsigned char *freemem;
680 static size_t freemem_size;
682 const unsigned char *inbuf;
683 unsigned char *outbuf;
684 int malloc_count;
685 transmem_block_t *transmem_list = NULL;
687 inbuf = (const unsigned char *) result;
688 outbuf = freemem + sizeof(size_t);
690 malloc_count = 0;
691 while (1) {
692 transmem_block_t *newmem;
693 ICONV_CONST char *inptr = (ICONV_CONST char *) inbuf;
694 size_t inleft = resultlen;
695 char *outptr = (unsigned char *) outbuf;
696 size_t outleft;
698 if (freemem_size < sizeof(size_t))
699 goto resize_freemem;
701 outleft = freemem_size - sizeof(size_t);
702 if (iconv(domain->conv, &inptr, &inleft,
703 &outptr, &outleft)
704 != (size_t) (-1)) {
705 outbuf = (unsigned char *) outptr;
706 break;
708 if (errno != E2BIG) {
709 goto converted;
712 resize_freemem:
713 /* We must allocate a new buffer or resize the old one. */
714 if (malloc_count > 0) {
715 ++malloc_count;
716 freemem_size =
717 malloc_count *
718 INITIAL_BLOCK_SIZE;
719 newmem = (transmem_block_t *)
720 realloc(transmem_list,
721 freemem_size);
722 } else {
723 malloc_count = 1;
724 freemem_size = INITIAL_BLOCK_SIZE;
725 newmem = (transmem_block_t *)
726 malloc(freemem_size);
728 if (newmem == NULL) {
729 freemem = NULL;
730 freemem_size = 0;
731 goto converted;
733 transmem_list = newmem;
734 freemem = newmem;
735 outbuf = freemem + sizeof(size_t);
738 /* We have now in our buffer a converted string. Put this
739 into the table of conversions. */
740 *(size_t *) freemem = outbuf - freemem - sizeof(size_t);
741 domain->conv_tab[act] = (unsigned char *) freemem;
742 /* Shrink freemem, but keep it aligned. */
743 freemem_size -= outbuf - freemem;
744 freemem = outbuf;
745 freemem += freemem_size & (alignof(size_t) - 1);
746 freemem_size = freemem_size & ~(alignof(size_t) - 1);
750 /* Now domain->conv_tab[act] contains the translation of all
751 the plural variants. */
752 result = domain->conv_tab[act] + sizeof(size_t);
753 resultlen = *(size_t *) domain->conv_tab[act];
756 converted:
757 /* The result string is converted. */
759 #endif /* HAVE_ICONV */
761 *lengthp = resultlen;
762 return result;
765 /* Look up a plural variant. */
766 static unsigned char *
767 plural_lookup(struct loaded_l10nfile *domain, unsigned long int n,
768 const unsigned char *translation, size_t translation_len)
770 struct loaded_domain *domaindata =
771 (struct loaded_domain *) domain->data;
772 unsigned long int indexx;
773 const unsigned char *p;
775 indexx = plural_eval(domaindata->plural, n);
776 if (indexx >= domaindata->nplurals)
777 /* This should never happen. It means the plural expression and the
778 given maximum value do not match. */
779 indexx = 0;
781 /* Skip INDEX strings at TRANSLATION. */
782 p = translation;
783 while (indexx-- > 0) {
784 p = strchr(p, '\0');
786 /* And skip over the NUL byte. */
787 p++;
789 if (p >= translation + translation_len)
790 /* This should never happen. It means the plural expression
791 evaluated to a value larger than the number of variants
792 available for MSGID1. */
793 return (unsigned char *) translation;
795 return (unsigned char *) p;
798 /* Function to evaluate the plural expression and return an index value. */
799 static unsigned long int
800 plural_eval(struct expression *pexp, unsigned long int n)
802 switch (pexp->nargs) {
803 case 0:
804 switch (pexp->operation) {
805 case var:
806 return n;
807 case num:
808 return pexp->val.num;
809 default:
810 break;
812 /* NOTREACHED */
813 break;
814 case 1:
816 /* pexp->operation must be lnot. */
817 unsigned long int arg =
818 plural_eval(pexp->val.args[0], n);
819 return !arg;
821 case 2:
823 unsigned long int leftarg =
824 plural_eval(pexp->val.args[0], n);
825 if (pexp->operation == lor)
826 return leftarg
827 || plural_eval(pexp->val.
828 args[1], n);
829 else if (pexp->operation == land)
830 return leftarg
831 && plural_eval(pexp->val.
832 args[1], n);
833 else {
834 unsigned long int rightarg =
835 plural_eval(pexp->val.args[1],
838 switch (pexp->operation) {
839 case mult:
840 return leftarg *
841 rightarg;
842 case divide:
843 return leftarg /
844 rightarg;
845 case module:
846 return leftarg %
847 rightarg;
848 case plus:
849 return leftarg +
850 rightarg;
851 case minus:
852 return leftarg -
853 rightarg;
854 case less_than:
855 return leftarg <
856 rightarg;
857 case greater_than:
858 return leftarg >
859 rightarg;
860 case less_or_equal:
861 return leftarg <=
862 rightarg;
863 case greater_or_equal:
864 return leftarg >=
865 rightarg;
866 case equal:
867 return leftarg ==
868 rightarg;
869 case not_equal:
870 return leftarg !=
871 rightarg;
872 default:
873 break;
876 /* NOTREACHED */
877 break;
879 case 3:
881 /* pexp->operation must be qmop. */
882 unsigned long int boolarg =
883 plural_eval(pexp->val.args[0], n);
884 return plural_eval(pexp->val.
885 args[boolarg ? 1 : 2], n);
888 /* NOTREACHED */
889 return 0;
892 /* Return string representation of locale CATEGORY. */
893 static const unsigned char *
894 category_to_name(int category)
896 const unsigned char *retval;
898 switch (category) {
899 #ifdef LC_COLLATE
900 case LC_COLLATE:
901 retval = "LC_COLLATE";
902 break;
903 #endif
904 #ifdef LC_CTYPE
905 case LC_CTYPE:
906 retval = "LC_CTYPE";
907 break;
908 #endif
909 #ifdef LC_MONETARY
910 case LC_MONETARY:
911 retval = "LC_MONETARY";
912 break;
913 #endif
914 #ifdef LC_NUMERIC
915 case LC_NUMERIC:
916 retval = "LC_NUMERIC";
917 break;
918 #endif
919 #ifdef LC_TIME
920 case LC_TIME:
921 retval = "LC_TIME";
922 break;
923 #endif
924 #ifdef LC_MESSAGES
925 case LC_MESSAGES:
926 retval = "LC_MESSAGES";
927 break;
928 #endif
929 #ifdef LC_RESPONSE
930 case LC_RESPONSE:
931 retval = "LC_RESPONSE";
932 break;
933 #endif
934 #ifdef LC_ALL
935 case LC_ALL:
936 /* This might not make sense but is perhaps better than any other
937 value. */
938 retval = "LC_ALL";
939 break;
940 #endif
941 default:
942 /* If you have a better idea for a default value let me know. */
943 retval = "LC_XXX";
946 return retval;
949 /* Guess value of current locale from value of the environment variables. */
950 static const unsigned char *
951 guess_category_value(int category, const unsigned char *categoryname)
953 const unsigned char *language;
954 const unsigned char *retval;
956 /* Takes precedence to anything else, damn it's what the application wants!
957 * ;-) --pasky */
958 if (LANGUAGE && *LANGUAGE)
959 return LANGUAGE;
961 /* The highest priority value is the `LANGUAGE' environment
962 variable. But we don't use the value if the currently selected
963 locale is the C locale. This is a GNU extension. */
964 /* XXX: This GNU extension breaks things for me and I can't see what is it
965 * good for - I think it only makes things more difficult and arcane, since
966 * it requires you to set up more variables than LANGUAGE, it's poorly
967 * documented and so on. If this breaks anything, let me know at
968 * pasky@ucw.cz, I'm really curious. If LANGUAGE exists, we just use it and
969 * do no more tests. This is an ELinks extension. --pasky */
970 language = getenv("LANGUAGE");
971 if (language && language[0])
972 return language;
974 /* We have to proceed with the POSIX methods of looking to `LC_ALL',
975 `LC_xxx', and `LANG'. On some systems this can be done by the
976 `setlocale' function itself. */
977 #if (defined HAVE_SETLOCALE && defined HAVE_LC_MESSAGES && defined HAVE_LOCALE_NULL)
978 retval = setlocale(category, NULL);
979 #else
980 /* Setting of LC_ALL overwrites all other. */
981 retval = getenv("LC_ALL");
982 if (retval == NULL || retval[0] == '\0') {
983 /* Next comes the name of the desired category. */
984 retval = getenv(categoryname);
985 if (retval == NULL || retval[0] == '\0') {
986 /* Last possibility is the LANG environment variable. */
987 retval = getenv("LANG");
988 if (retval == NULL || retval[0] == '\0')
989 /* We use C as the default domain. POSIX says this is
990 implementation defined. */
991 retval = "C";
994 #endif
996 return retval;