Retry only for https protocol
[elinks.git] / src / intl / gettext / dcigettext.c
blob55a7b8e3ed177b126fd28b6f671e1e6217baadd0
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 ((const char *)(P), '/') || strchr ((const char *)(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 ((const char *)(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 char _AAA_nl_default_default_domain__[] = "messages";
192 const unsigned char *_nl_default_default_domain__ = (const unsigned char *)_AAA_nl_default_default_domain__;
194 /* Value used as the default domain for gettext(3). */
195 const unsigned char *_nl_current_default_domain__ = (const unsigned char *)_AAA_nl_default_default_domain__;
197 /* Contains the default location of the message catalogs. */
198 const char _AAA_nl_default_dirname__[] = LOCALEDIR;
200 const unsigned char *_nl_default_dirname__ = (const unsigned char *)_AAA_nl_default_dirname__;
202 /* Contains application-specific LANGUAGE variation, taking precedence to the
203 * $LANGUAGE environment variable. */
204 unsigned char *LANGUAGE = NULL;
206 /* List with bindings of specific domains created by bindtextdomain()
207 calls. */
208 struct binding *_nl_domain_bindings__;
210 /* Prototypes for local functions. */
211 static unsigned char *plural_lookup(struct loaded_l10nfile * domain,
212 unsigned long int n,
213 const unsigned char *translation,
214 size_t translation_len);
215 static unsigned long int plural_eval(struct expression * pexp,
216 unsigned long int n);
217 static const unsigned char *category_to_name(int category);
218 static const unsigned char *guess_category_value(int category,
219 const unsigned char *categoryname);
221 /* For those loosing systems which don't have `alloca' we have to add
222 some additional code emulating it. */
223 #ifdef HAVE_ALLOCA
224 /* Nothing has to be done. */
225 #define ADD_BLOCK(list, address) /* nothing */
226 #define FREE_BLOCKS(list) /* nothing */
227 #else
228 struct block_list {
229 void *address;
230 struct block_list *next;
232 #define ADD_BLOCK(list, addr) \
233 do { \
234 struct block_list *newp = (struct block_list *) malloc(sizeof(*newp)); \
235 /* If we cannot get a free block we cannot add the new element to \
236 the list. */ \
237 if (newp != NULL) { \
238 newp->address = (addr); \
239 newp->next = (list); \
240 (list) = newp; \
242 } while (0)
243 #define FREE_BLOCKS(list) \
244 do { \
245 while (list != NULL) { \
246 struct block_list *old = list; \
247 list = list->next; \
248 free (old->address); \
249 free (old); \
251 } while (0)
252 #undef alloca
253 #define alloca(size) (malloc (size))
254 #endif /* have alloca */
256 typedef unsigned char transmem_block_t;
258 /* Checking whether the binaries runs SUID must be done and glibc provides
259 easier methods therefore we make a difference here. */
260 #ifndef HAVE_GETUID
261 #define getuid() 0
262 #endif
264 #ifndef HAVE_GETGID
265 #define getgid() 0
266 #endif
268 #ifndef HAVE_GETEUID
269 #define geteuid() getuid()
270 #endif
272 #ifndef HAVE_GETEGID
273 #define getegid() getgid()
274 #endif
276 static int enable_secure;
278 #define ENABLE_SECURE (enable_secure == 1)
279 #define DETERMINE_SECURE \
280 if (enable_secure == 0) \
282 if (getuid () != geteuid () || getgid () != getegid ()) \
283 enable_secure = 1; \
284 else \
285 enable_secure = -1; \
288 /* Look up MSGID in the DOMAINNAME message catalog for the current
289 CATEGORY locale and, if PLURAL is nonzero, search over string
290 depending on the plural form determined by N. */
291 unsigned char *
292 dcigettext__(const unsigned char *domainname, const unsigned char *msgid1, const unsigned char *msgid2,
293 int plural, unsigned long int n, int category)
295 #ifndef HAVE_ALLOCA
296 struct block_list *block_list = NULL;
297 #endif
298 struct loaded_l10nfile *domain;
299 struct binding *binding;
300 const unsigned char *categoryname;
301 const unsigned char *categoryvalue;
302 unsigned char *dirname, *xdomainname;
303 unsigned char *single_locale;
304 unsigned char *retval;
305 size_t retlen;
306 int saved_errno;
308 #if defined HAVE_TSEARCH
309 struct known_translation_t *search;
310 struct known_translation_t **foundp = NULL;
311 size_t msgid_len;
312 #endif
313 size_t domainname_len;
315 /* If no real MSGID is given return NULL. */
316 if (msgid1 == NULL)
317 return NULL;
319 /* If DOMAINNAME is NULL, we are interested in the default domain. If
320 CATEGORY is not LC_MESSAGES this might not make much sense but the
321 definition left this undefined. */
322 if (domainname == NULL)
323 domainname = _nl_current_default_domain__;
325 #if defined HAVE_TSEARCH
326 msgid_len = strlen(msgid1) + 1;
328 /* Try to find the translation among those which we found at
329 some time. */
330 search = (struct known_translation_t *)
331 alloca(offsetof(struct known_translation_t, msgid) + msgid_len);
333 memcpy(search->msgid, msgid1, msgid_len);
334 search->domainname = (unsigned char *) domainname;
335 search->category = category;
337 foundp = (struct known_translation_t **) tfind(search, &root, transcmp);
338 if (foundp != NULL && (*foundp)->counter == _nl_msg_cat_cntr) {
339 /* Now deal with plural. */
340 if (plural)
341 retval = plural_lookup((*foundp)->domain, n,
342 (*foundp)->translation,
343 (*foundp)->translation_length);
344 else
345 retval = (unsigned char *) (*foundp)->translation;
347 return retval;
349 #endif
351 /* Preserve the `errno' value. */
352 saved_errno = errno;
354 /* See whether this is a SUID binary or not. */
355 DETERMINE_SECURE;
357 /* First find matching binding. */
358 for (binding = _nl_domain_bindings__; binding != NULL;
359 binding = binding->next) {
360 int compare = strcmp(domainname, binding->domainname);
362 if (compare == 0)
363 /* We found it! */
364 break;
365 if (compare < 0) {
366 /* It is not in the list. */
367 binding = NULL;
368 break;
372 if (binding == NULL)
373 dirname = (unsigned char *) _nl_default_dirname__;
374 else if (IS_ABSOLUTE_PATH(binding->dirname))
375 dirname = binding->dirname;
376 else {
377 /* We have a relative path. Make it absolute now. */
378 size_t dirname_len = strlen(binding->dirname) + 1;
379 size_t path_max;
380 unsigned char *ret;
382 path_max = (unsigned int) PATH_MAX;
383 path_max += 2; /* The getcwd docs say to do this. */
385 for (;;) {
386 dirname = (unsigned char *) alloca(path_max + dirname_len);
387 ADD_BLOCK(block_list, dirname);
389 errno = 0;
390 ret = getcwd(dirname, path_max);
391 if (ret != NULL || errno != ERANGE)
392 break;
394 path_max += path_max / 2;
395 path_max += PATH_INCR;
398 if (ret == NULL) {
399 /* We cannot get the current working directory. Don't signal an
400 error but simply return the default string. */
401 FREE_BLOCKS(block_list);
402 errno = saved_errno;
403 return (plural == 0 ? (unsigned char *) msgid1
404 /* Use the Germanic plural rule. */
405 : n == 1 ? (unsigned char *) msgid1 : (unsigned char *) msgid2);
408 stpcpy(stpcpy(strchr((const char *)dirname, '\0'), "/"), binding->dirname);
411 /* Now determine the symbolic name of CATEGORY and its value. */
412 categoryname = category_to_name(category);
413 categoryvalue = guess_category_value(category, categoryname);
415 domainname_len = strlen(domainname);
416 xdomainname = (unsigned char *) alloca(strlen(categoryname)
417 + domainname_len + 5);
418 ADD_BLOCK(block_list, xdomainname);
420 stpcpy(mempcpy(stpcpy(stpcpy(xdomainname, categoryname), "/"),
421 domainname, domainname_len), ".mo");
423 /* Creating working area. */
424 single_locale = (unsigned char *) alloca(strlen(categoryvalue) + 1);
425 ADD_BLOCK(block_list, single_locale);
427 /* Search for the given string. This is a loop because we perhaps
428 got an ordered list of languages to consider for the translation. */
429 while (1) {
430 /* Make CATEGORYVALUE point to the next element of the list. */
431 while (categoryvalue[0] != '\0' && categoryvalue[0] == ':')
432 ++categoryvalue;
433 if (categoryvalue[0] == '\0') {
434 /* The whole contents of CATEGORYVALUE has been searched but
435 no valid entry has been found. We solve this situation
436 by implicitly appending a "C" entry, i.e. no translation
437 will take place. */
438 single_locale[0] = 'C';
439 single_locale[1] = '\0';
440 } else {
441 unsigned char *cp = single_locale;
443 while (categoryvalue[0] != '\0'
444 && categoryvalue[0] != ':')
445 *cp++ = *categoryvalue++;
446 *cp = '\0';
448 /* When this is a SUID binary we must not allow accessing files
449 outside the dedicated directories. */
450 if (ENABLE_SECURE && IS_PATH_WITH_DIR(single_locale))
451 /* Ingore this entry. */
452 continue;
455 /* If the current locale value is C (or POSIX) we don't load a
456 domain. Return the MSGID. */
457 if (strcmp(single_locale, "C") == 0
458 || strcmp(single_locale, "POSIX") == 0) {
459 FREE_BLOCKS(block_list);
460 errno = saved_errno;
461 return (plural == 0 ? (unsigned char *) msgid1
462 /* Use the Germanic plural rule. */
463 : n == 1 ? (unsigned char *) msgid1 : (unsigned char *) msgid2);
466 /* Find structure describing the message catalog matching the
467 DOMAINNAME and CATEGORY. */
468 domain = _nl_find_domain(dirname, single_locale, xdomainname,
469 binding);
471 if (domain != NULL) {
472 retval = _nl_find_msg(domain, binding, msgid1, &retlen);
474 if (retval == NULL) {
475 int cnt;
477 for (cnt = 0; domain->successor[cnt] != NULL;
478 ++cnt) {
479 retval = _nl_find_msg(domain->
480 successor[cnt],
481 binding, msgid1,
482 &retlen);
484 if (retval != NULL) {
485 domain = domain->successor[cnt];
486 break;
491 if (retval != NULL) {
492 /* Found the translation of MSGID1 in domain DOMAIN:
493 starting at RETVAL, RETLEN bytes. */
494 FREE_BLOCKS(block_list);
495 errno = saved_errno;
496 #if defined HAVE_TSEARCH
497 if (foundp == NULL) {
498 /* Create a new entry and add it to the search tree. */
499 struct known_translation_t *newp;
501 newp = (struct known_translation_t *)
502 malloc(offsetof
503 (struct
504 known_translation_t,
505 msgid)
506 + msgid_len +
507 domainname_len + 1);
508 if (newp != NULL) {
509 newp->domainname =
510 mempcpy(newp->msgid,
511 msgid1,
512 msgid_len);
513 memcpy(newp->domainname,
514 domainname,
515 domainname_len + 1);
516 newp->category = category;
517 newp->counter =
518 _nl_msg_cat_cntr;
519 newp->domain = domain;
520 newp->translation = retval;
521 newp->translation_length =
522 retlen;
524 /* Insert the entry in the search tree. */
525 foundp = (struct
526 known_translation_t
528 tsearch(newp, &root,
529 transcmp);
530 if (foundp == NULL
531 || *foundp != newp)
532 /* The insert failed. */
533 free(newp);
535 } else {
536 /* We can update the existing entry. */
537 (*foundp)->counter = _nl_msg_cat_cntr;
538 (*foundp)->domain = domain;
539 (*foundp)->translation = retval;
540 (*foundp)->translation_length = retlen;
542 #endif
543 /* Now deal with plural. */
544 if (plural)
545 retval = plural_lookup(domain, n,
546 retval, retlen);
548 return retval;
552 /* NOTREACHED */
555 unsigned char *
556 _nl_find_msg(struct loaded_l10nfile *domain_file,
557 struct binding *domainbinding,
558 const unsigned char *msgid, size_t *lengthp)
560 struct loaded_domain *domain;
561 size_t act;
562 unsigned char *result;
563 size_t resultlen;
565 if (domain_file->decided == 0)
566 _nl_load_domain(domain_file, domainbinding);
568 if (domain_file->data == NULL)
569 return NULL;
571 domain = (struct loaded_domain *) domain_file->data;
573 /* Locate the MSGID and its translation. */
574 if (domain->hash_size > 2 && domain->hash_tab != NULL) {
575 /* Use the hashing table. */
576 nls_uint32 len = strlen(msgid);
577 nls_uint32 hash_val = hash_string(msgid);
578 nls_uint32 idx = hash_val % domain->hash_size;
579 nls_uint32 incr = 1 + (hash_val % (domain->hash_size - 2));
581 while (1) {
582 nls_uint32 nstr =
583 W(domain->must_swap, domain->hash_tab[idx]);
585 if (nstr == 0)
586 /* Hash table entry is empty. */
587 return NULL;
589 /* Compare msgid with the original string at index nstr-1.
590 We compare the lengths with >=, not ==, because plural entries
591 are represented by strings with an embedded NUL. */
592 if (W
593 (domain->must_swap,
594 domain->orig_tab[nstr - 1].length) >= len
596 (strcmp
597 (msgid,
598 domain->data + W(domain->must_swap,
599 domain->orig_tab[nstr -
600 1].offset))
601 == 0)) {
602 act = nstr - 1;
603 goto found;
606 if (idx >= domain->hash_size - incr)
607 idx -= domain->hash_size - incr;
608 else
609 idx += incr;
611 /* NOTREACHED */
612 } else {
613 /* Try the default method: binary search in the sorted array of
614 messages. */
615 size_t top, bottom;
617 bottom = 0;
618 top = domain->nstrings;
619 while (bottom < top) {
620 int cmp_val;
622 act = (bottom + top) / 2;
623 cmp_val = strcmp(msgid, (domain->data
624 + W(domain->must_swap,
625 domain->orig_tab[act].
626 offset)));
627 if (cmp_val < 0)
628 top = act;
629 else if (cmp_val > 0)
630 bottom = act + 1;
631 else
632 goto found;
634 /* No translation was found. */
635 return NULL;
638 found:
639 /* The translation was found at index ACT. If we have to convert the
640 string to use a different character set, this is the time. */
641 result = ((unsigned char *) domain->data
642 + W(domain->must_swap, domain->trans_tab[act].offset));
643 resultlen = W(domain->must_swap, domain->trans_tab[act].length) + 1;
645 #if HAVE_ICONV
646 if (domain->codeset_cntr
647 != (domainbinding != NULL ? domainbinding->codeset_cntr : 0)) {
648 /* The domain's codeset has changed through bind_textdomain_codeset()
649 since the message catalog was initialized or last accessed. We
650 have to reinitialize the converter. */
651 _nl_free_domain_conv(domain);
652 _nl_init_domain_conv(domain_file, domain, domainbinding);
655 if (domain->conv != (iconv_t) - 1) {
656 /* We are supposed to do a conversion. First allocate an
657 appropriate table with the same structure as the table
658 of translations in the file, where we can put the pointers
659 to the converted strings in.
660 There is a slight complication with plural entries. They
661 are represented by consecutive NUL terminated strings. We
662 handle this case by converting RESULTLEN bytes, including
663 NULs. */
665 if (domain->conv_tab == NULL
666 && ((domain->conv_tab = (unsigned char **) calloc(domain->nstrings,
667 sizeof(unsigned char *)))
668 == NULL))
669 /* Mark that we didn't succeed allocating a table. */
670 domain->conv_tab = (unsigned char **) -1;
672 if (domain->conv_tab == (unsigned char **) -1)
673 /* Nothing we can do, no more memory. */
674 goto converted;
676 if (domain->conv_tab[act] == NULL) {
677 /* We haven't used this string so far, so it is not
678 translated yet. Do this now. */
679 /* We use a bit more efficient memory handling.
680 We allocate always larger blocks which get used over
681 time. This is faster than many small allocations. */
682 #define INITIAL_BLOCK_SIZE 4080
683 static unsigned char *freemem;
684 static size_t freemem_size;
686 const unsigned char *inbuf;
687 unsigned char *outbuf;
688 int malloc_count;
689 transmem_block_t *transmem_list = NULL;
691 inbuf = (const unsigned char *) result;
692 outbuf = freemem + sizeof(size_t);
694 malloc_count = 0;
695 while (1) {
696 transmem_block_t *newmem;
697 ICONV_CONST char *inptr = (ICONV_CONST char *) inbuf;
698 size_t inleft = resultlen;
699 char *outptr = (unsigned char *) outbuf;
700 size_t outleft;
702 if (freemem_size < sizeof(size_t))
703 goto resize_freemem;
705 outleft = freemem_size - sizeof(size_t);
706 if (iconv(domain->conv, &inptr, &inleft,
707 &outptr, &outleft)
708 != (size_t) (-1)) {
709 outbuf = (unsigned char *) outptr;
710 break;
712 if (errno != E2BIG) {
713 goto converted;
716 resize_freemem:
717 /* We must allocate a new buffer or resize the old one. */
718 if (malloc_count > 0) {
719 ++malloc_count;
720 freemem_size =
721 malloc_count *
722 INITIAL_BLOCK_SIZE;
723 newmem = (transmem_block_t *)
724 realloc(transmem_list,
725 freemem_size);
726 } else {
727 malloc_count = 1;
728 freemem_size = INITIAL_BLOCK_SIZE;
729 newmem = (transmem_block_t *)
730 malloc(freemem_size);
732 if (newmem == NULL) {
733 freemem = NULL;
734 freemem_size = 0;
735 goto converted;
737 transmem_list = newmem;
738 freemem = newmem;
739 outbuf = freemem + sizeof(size_t);
742 /* We have now in our buffer a converted string. Put this
743 into the table of conversions. */
744 *(size_t *) freemem = outbuf - freemem - sizeof(size_t);
745 domain->conv_tab[act] = (unsigned char *) freemem;
746 /* Shrink freemem, but keep it aligned. */
747 freemem_size -= outbuf - freemem;
748 freemem = outbuf;
749 freemem += freemem_size & (alignof(size_t) - 1);
750 freemem_size = freemem_size & ~(alignof(size_t) - 1);
754 /* Now domain->conv_tab[act] contains the translation of all
755 the plural variants. */
756 result = domain->conv_tab[act] + sizeof(size_t);
757 resultlen = *(size_t *) domain->conv_tab[act];
760 converted:
761 /* The result string is converted. */
763 #endif /* HAVE_ICONV */
765 *lengthp = resultlen;
766 return result;
769 /* Look up a plural variant. */
770 static unsigned char *
771 plural_lookup(struct loaded_l10nfile *domain, unsigned long int n,
772 const unsigned char *translation, size_t translation_len)
774 struct loaded_domain *domaindata =
775 (struct loaded_domain *) domain->data;
776 unsigned long int indexx;
777 const unsigned char *p;
779 indexx = plural_eval(domaindata->plural, n);
780 if (indexx >= domaindata->nplurals)
781 /* This should never happen. It means the plural expression and the
782 given maximum value do not match. */
783 indexx = 0;
785 /* Skip INDEX strings at TRANSLATION. */
786 p = translation;
787 while (indexx-- > 0) {
788 p = strchr((const char *)p, '\0');
790 /* And skip over the NUL byte. */
791 p++;
793 if (p >= translation + translation_len)
794 /* This should never happen. It means the plural expression
795 evaluated to a value larger than the number of variants
796 available for MSGID1. */
797 return (unsigned char *) translation;
799 return (unsigned char *) p;
802 /* Function to evaluate the plural expression and return an index value. */
803 static unsigned long int
804 plural_eval(struct expression *pexp, unsigned long int n)
806 switch (pexp->nargs) {
807 case 0:
808 switch (pexp->operation) {
809 case var:
810 return n;
811 case num:
812 return pexp->val.num;
813 default:
814 break;
816 /* NOTREACHED */
817 break;
818 case 1:
820 /* pexp->operation must be lnot. */
821 unsigned long int arg =
822 plural_eval(pexp->val.args[0], n);
823 return !arg;
825 case 2:
827 unsigned long int leftarg =
828 plural_eval(pexp->val.args[0], n);
829 if (pexp->operation == lor)
830 return leftarg
831 || plural_eval(pexp->val.
832 args[1], n);
833 else if (pexp->operation == land)
834 return leftarg
835 && plural_eval(pexp->val.
836 args[1], n);
837 else {
838 unsigned long int rightarg =
839 plural_eval(pexp->val.args[1],
842 switch (pexp->operation) {
843 case mult:
844 return leftarg *
845 rightarg;
846 case divide:
847 return leftarg /
848 rightarg;
849 case module:
850 return leftarg %
851 rightarg;
852 case plus:
853 return leftarg +
854 rightarg;
855 case minus:
856 return leftarg -
857 rightarg;
858 case less_than:
859 return leftarg <
860 rightarg;
861 case greater_than:
862 return leftarg >
863 rightarg;
864 case less_or_equal:
865 return leftarg <=
866 rightarg;
867 case greater_or_equal:
868 return leftarg >=
869 rightarg;
870 case equal:
871 return leftarg ==
872 rightarg;
873 case not_equal:
874 return leftarg !=
875 rightarg;
876 default:
877 break;
880 /* NOTREACHED */
881 break;
883 case 3:
885 /* pexp->operation must be qmop. */
886 unsigned long int boolarg =
887 plural_eval(pexp->val.args[0], n);
888 return plural_eval(pexp->val.
889 args[boolarg ? 1 : 2], n);
892 /* NOTREACHED */
893 return 0;
896 /* Return string representation of locale CATEGORY. */
897 static const unsigned char *
898 category_to_name(int category)
900 const unsigned char *retval;
902 switch (category) {
903 #ifdef LC_COLLATE
904 case LC_COLLATE:
905 retval = "LC_COLLATE";
906 break;
907 #endif
908 #ifdef LC_CTYPE
909 case LC_CTYPE:
910 retval = "LC_CTYPE";
911 break;
912 #endif
913 #ifdef LC_MONETARY
914 case LC_MONETARY:
915 retval = "LC_MONETARY";
916 break;
917 #endif
918 #ifdef LC_NUMERIC
919 case LC_NUMERIC:
920 retval = "LC_NUMERIC";
921 break;
922 #endif
923 #ifdef LC_TIME
924 case LC_TIME:
925 retval = "LC_TIME";
926 break;
927 #endif
928 #ifdef LC_MESSAGES
929 case LC_MESSAGES:
930 retval = "LC_MESSAGES";
931 break;
932 #endif
933 #ifdef LC_RESPONSE
934 case LC_RESPONSE:
935 retval = "LC_RESPONSE";
936 break;
937 #endif
938 #ifdef LC_ALL
939 case LC_ALL:
940 /* This might not make sense but is perhaps better than any other
941 value. */
942 retval = "LC_ALL";
943 break;
944 #endif
945 default:
946 /* If you have a better idea for a default value let me know. */
947 retval = "LC_XXX";
950 return retval;
953 /* Guess value of current locale from value of the environment variables. */
954 static const unsigned char *
955 guess_category_value(int category, const unsigned char *categoryname)
957 const unsigned char *language;
958 const unsigned char *retval;
960 /* Takes precedence to anything else, damn it's what the application wants!
961 * ;-) --pasky */
962 if (LANGUAGE && *LANGUAGE)
963 return LANGUAGE;
965 /* The highest priority value is the `LANGUAGE' environment
966 variable. But we don't use the value if the currently selected
967 locale is the C locale. This is a GNU extension. */
968 /* XXX: This GNU extension breaks things for me and I can't see what is it
969 * good for - I think it only makes things more difficult and arcane, since
970 * it requires you to set up more variables than LANGUAGE, it's poorly
971 * documented and so on. If this breaks anything, let me know at
972 * pasky@ucw.cz, I'm really curious. If LANGUAGE exists, we just use it and
973 * do no more tests. This is an ELinks extension. --pasky */
974 language = getenv("LANGUAGE");
975 if (language && language[0])
976 return language;
978 /* We have to proceed with the POSIX methods of looking to `LC_ALL',
979 `LC_xxx', and `LANG'. On some systems this can be done by the
980 `setlocale' function itself. */
981 #if (defined HAVE_SETLOCALE && defined HAVE_LC_MESSAGES && defined HAVE_LOCALE_NULL)
982 retval = setlocale(category, NULL);
983 #else
984 /* Setting of LC_ALL overwrites all other. */
985 retval = getenv("LC_ALL");
986 if (retval == NULL || retval[0] == '\0') {
987 /* Next comes the name of the desired category. */
988 retval = getenv(categoryname);
989 if (retval == NULL || retval[0] == '\0') {
990 /* Last possibility is the LANG environment variable. */
991 retval = getenv("LANG");
992 if (retval == NULL || retval[0] == '\0')
993 /* We use C as the default domain. POSIX says this is
994 implementation defined. */
995 retval = "C";
998 #endif
1000 return retval;