Updated from /src/gettext-0.10.8/intl/finddomain.c
[kopensolaris-gnu/glibc.git] / intl / finddomain.c
1 /* finddomain.c -- handle list of needed message catalogs
2    Copyright (C) 1995, 1996 Free Software Foundation, Inc.
3    Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995.
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 #include <errno.h>
24 #include <stdio.h>
25 #include <sys/types.h>
26
27 #if defined STDC_HEADERS || defined _LIBC
28 # include <stdlib.h>
29 #else
30 # ifdef HAVE_MALLOC_H
31 #  include <malloc.h>
32 # else
33 void free ();
34 # endif
35 #endif
36
37 #if defined HAVE_STRING_H || defined _LIBC
38 # include <string.h>
39 #else
40 # include <strings.h>
41 #endif
42 #if !HAVE_STRCHR && !defined _LIBC
43 # ifndef strchr
44 #  define strchr index
45 # endif
46 #endif
47
48 #if defined HAVE_UNISTD_H || defined _LIBC
49 # include <unistd.h>
50 #endif
51
52 #include "gettext.h"
53 #include "gettextP.h"
54 #ifdef _LIBC
55 # include <libintl.h>
56 #else
57 # include "libgettext.h"
58 #endif
59
60 /* @@ end of prolog @@ */
61
62 #ifdef _LIBC
63 /* Rename the non ANSI C functions.  This is required by the standard
64    because some ANSI C functions will require linking with this object
65    file and the name space must not be polluted.  */
66 # define stpcpy(dest, src) __stpcpy(dest, src)
67 #endif
68
69 /* Encoding of locale name parts.  */
70 #define CEN_REVISION            1
71 #define CEN_SPONSOR             2
72 #define CEN_SPECIAL             4
73 #define XPG_NORM_CODESET        8
74 #define XPG_CODESET             16
75 #define TERRITORY               32
76 #define CEN_AUDIENCE            64
77 #define XPG_MODIFIER            128
78
79 #define CEN_SPECIFIC    (CEN_REVISION|CEN_SPONSOR|CEN_SPECIAL|CEN_AUDIENCE)
80 #define XPG_SPECIFIC    (XPG_CODESET|XPG_NORM_CODESET|XPG_MODIFIER)
81
82
83 /* List of already loaded domains.  */
84 static struct loaded_domain *_nl_loaded_domains;
85
86 /* Prototypes for local functions.  */
87 static struct loaded_domain *make_entry_rec PARAMS ((const char *dirname,
88                                                      int mask,
89                                                      const char *language,
90                                                      const char *territory,
91                                                      const char *codeset,
92                                                      const char *normalized_codeset,
93                                                      const char *modifier,
94                                                      const char *special,
95                                                      const char *sponsor,
96                                                      const char *revision,
97                                                      const char *domainname,
98                                                      int do_allocate));
99
100 /* Normalize name of selected codeset.  */
101 static const char *normalize_codeset PARAMS ((const char *codeset));
102
103 /* Substitution for systems lacking this function in their C library.  */
104 #if !_LIBC && !HAVE_STPCPY
105 static char *stpcpy__ PARAMS ((char *dest, const char *src));
106 # define stpcpy(dest, src) stpcpy__ (dest, src)
107 #endif
108
109
110 /* Return a data structure describing the message catalog described by
111    the DOMAINNAME and CATEGORY parameters with respect to the currently
112    established bindings.  */
113 struct loaded_domain *
114 _nl_find_domain (dirname, locale, domainname)
115      const char *dirname;
116      char *locale;
117      const char *domainname;
118 {
119   enum { undecided, xpg, cen } syntax;
120   struct loaded_domain *retval;
121   const char *language;
122   const char *modifier = NULL;
123   const char *territory = NULL;
124   const char *codeset = NULL;
125   const char *normalized_codeset = NULL;
126   const char *special = NULL;
127   const char *sponsor = NULL;
128   const char *revision = NULL;
129   const char *alias_value = NULL;
130   char *cp;
131   int mask;
132
133   /* CATEGORYVALUE now possibly contains a colon separated list of
134      locales.  Each single locale can consist of up to four recognized
135      parts for the XPG syntax:
136
137                 language[_territory[.codeset]][@modifier]
138
139      and six parts for the CEN syntax:
140
141         language[_territory][+audience][+special][,sponsor][_revision]
142
143      Beside the first all of them are allowed to be missing.  If the
144      full specified locale is not found, the less specific one are
145      looked for.  The various part will be stripped of according to
146      the following order:
147                 (1) revision
148                 (2) sponsor
149                 (3) special
150                 (4) codeset
151                 (5) normalized codeset
152                 (6) territory
153                 (7) audience/modifier
154    */
155
156   /* If we have already tested for this locale entry there has to
157      be one data set in the list of loaded domains.  */
158   retval = make_entry_rec (dirname, 0, locale, NULL, NULL, NULL, NULL,
159                            NULL, NULL, NULL, domainname, 0);
160   if (retval != NULL)
161     {
162       /* We know something about this locale.  */
163       int cnt;
164
165       if (retval->decided == 0)
166         _nl_load_domain (retval); /* @@@ */
167
168       if (retval->data != NULL)
169         return retval;
170
171       for (cnt = 0; retval->successor[cnt] != NULL; ++cnt)
172         {
173           if (retval->successor[cnt]->decided == 0)
174             _nl_load_domain (retval->successor[cnt]);
175
176           if (retval->successor[cnt]->data != NULL)
177             break;
178         }
179
180       /* We really found some usable information.  */
181       return cnt >= 0 ? retval : NULL;
182       /* NOTREACHED */
183     }
184
185   /* See whether the locale value is an alias.  If yes its value
186      *overwrites* the alias name.  No test for the original value is
187      done.  */
188   alias_value = _nl_expand_alias (locale);
189   if (alias_value != NULL)
190     {
191       size_t len = strlen (alias_value) + 1;
192       locale = (char *) malloc (len);
193       if (locale == NULL)
194         return NULL;
195
196       memcpy (locale, alias_value, len);
197     }
198
199   /* Now we determine the single parts of the locale name.  First
200      look for the language.  Termination symbols are `_' and `@' if
201      we use XPG4 style, and `_', `+', and `,' if we use CEN syntax.  */
202   mask = 0;
203   syntax = undecided;
204   language = cp = locale;
205   while (cp[0] != '\0' && cp[0] != '_' && cp[0] != '@'
206          && cp[0] != '+' && cp[0] != ',')
207     ++cp;
208
209   if (language == cp)
210     /* This does not make sense: language has to be specified.  Use
211        this entry as it is without exploding.  Perhaps it is an alias.  */
212     cp = strchr (language, '\0');
213   else if (cp[0] == '_')
214     {
215       /* Next is the territory.  */
216       cp[0] = '\0';
217       territory = ++cp;
218
219       while (cp[0] != '\0' && cp[0] != '.' && cp[0] != '@'
220              && cp[0] != '+' && cp[0] != ',' && cp[0] != '_')
221         ++cp;
222
223       mask |= TERRITORY;
224
225       if (cp[0] == '.')
226         {
227           /* Next is the codeset.  */
228           syntax = xpg;
229           cp[0] = '\0';
230           codeset = ++cp;
231
232           while (cp[0] != '\0' && cp[0] != '@')
233             ++cp;
234
235           mask |= XPG_CODESET;
236
237           if (codeset != cp && codeset[0] != '\0')
238             {
239               normalized_codeset = normalize_codeset (codeset);
240               if (strcmp (codeset, normalized_codeset) == 0)
241                 free ((char *) normalized_codeset);
242               else
243                 mask |= XPG_NORM_CODESET;
244             }
245         }
246     }
247
248   if (cp[0] == '@' || (syntax != xpg && cp[0] == '+'))
249     {
250       /* Next is the modifier.  */
251       syntax = cp[0] == '@' ? xpg : cen;
252       cp[0] = '\0';
253       modifier = ++cp;
254
255       while (syntax == cen && cp[0] != '\0' && cp[0] != '+'
256              && cp[0] != ',' && cp[0] != '_')
257         ++cp;
258
259       mask |= XPG_MODIFIER | CEN_AUDIENCE;
260     }
261
262   if (syntax != xpg && (cp[0] == '+' || cp[0] == ',' || cp[0] == '_'))
263     {
264       syntax = cen;
265
266       if (cp[0] == '+')
267         {
268           /* Next is special application (CEN syntax).  */
269           cp[0] = '\0';
270           special = ++cp;
271
272           while (cp[0] != '\0' && cp[0] != ',' && cp[0] != '_')
273             ++cp;
274
275           mask |= CEN_SPECIAL;
276         }
277
278       if (cp[0] == ',')
279         {
280           /* Next is sponsor (CEN syntax).  */
281           cp[0] = '\0';
282           sponsor = ++cp;
283
284           while (cp[0] != '\0' && cp[0] != '_')
285             ++cp;
286
287           mask |= CEN_SPONSOR;
288         }
289
290       if (cp[0] == '_')
291         {
292           /* Next is revision (CEN syntax).  */
293           cp[0] = '\0';
294           revision = ++cp;
295
296           mask |= CEN_REVISION;
297         }
298     }
299
300   /* For CEN sytnax values it might be important to have the
301      separator character in the file name, not for XPG syntax.  */
302   if (syntax == xpg)
303     {
304       if (territory != NULL && territory[0] == '\0')
305         mask &= ~TERRITORY;
306
307       if (codeset != NULL && codeset[0] == '\0')
308         mask &= ~XPG_CODESET;
309
310       if (modifier != NULL && modifier[0] == '\0')
311         mask &= ~XPG_MODIFIER;
312     }
313
314   /* Create all possible locale entries which might be interested in
315      generalzation.  */
316   retval = make_entry_rec (dirname, mask, language, territory, codeset,
317                            normalized_codeset, modifier, special, sponsor,
318                            revision, domainname, 1);
319   if (retval == NULL)
320     /* This means we are out of core.  */
321     return NULL;
322
323   if (retval->decided == 0)
324     _nl_load_domain (retval);
325   if (retval->data == NULL)
326     {
327       int cnt;
328       for (cnt = 0; retval->successor[cnt] != NULL; ++cnt)
329         {
330           if (retval->successor[cnt]->decided == 0)
331             _nl_load_domain (retval->successor[cnt]);
332           if (retval->successor[cnt]->data != NULL)
333             break;
334
335           /* Signal that locale is not available.  */
336           retval->successor[cnt] = NULL;
337         }
338       if (retval->successor[cnt] == NULL)
339         retval = NULL;
340     }
341
342   /* The room for an alias was dynamically allocated.  Free it now.  */
343   if (alias_value != NULL)
344     free (locale);
345
346   return retval;
347 }
348
349
350 static struct loaded_domain *
351 make_entry_rec (dirname, mask, language, territory, codeset,
352                 normalized_codeset, modifier, special, sponsor, revision,
353                 domain, do_allocate)
354      const char *dirname;
355      int mask;
356      const char *language;
357      const char *territory;
358      const char *codeset;
359      const char *normalized_codeset;
360      const char *modifier;
361      const char *special;
362      const char *sponsor;
363      const char *revision;
364      const char *domain;
365      int do_allocate;
366 {
367   char *filename = NULL;
368   struct loaded_domain *last = NULL;
369   struct loaded_domain *retval;
370   char *cp;
371   size_t entries;
372   int cnt;
373
374
375   /* Process the current entry described by the MASK only when it is
376      valid.  Because the mask can have in the first call bits from
377      both syntaces set this is necessary to prevent constructing
378      illegal local names.  */
379   /* FIXME: Rewrite because test is necessary only in first round.  */
380   if ((mask & CEN_SPECIFIC) == 0 || (mask & XPG_SPECIFIC) == 0
381       || ((mask & XPG_CODESET) != 0 && (mask & XPG_NORM_CODESET) != 0))
382     {
383       /* Allocate room for the full file name.  */
384       filename = (char *) malloc (strlen (dirname) + 1
385                                   + strlen (language)
386                                   + ((mask & TERRITORY) != 0
387                                      ? strlen (territory) + 1 : 0)
388                                   + ((mask & XPG_CODESET) != 0
389                                      ? strlen (codeset) + 1 : 0)
390                                   + ((mask & XPG_NORM_CODESET) != 0
391                                      ? strlen (normalized_codeset) + 1 : 0)
392                                   + ((mask & XPG_MODIFIER) != 0 ?
393                                      strlen (modifier) + 1 : 0)
394                                   + ((mask & CEN_SPECIAL) != 0
395                                      ? strlen (special) + 1 : 0)
396                                   + ((mask & CEN_SPONSOR) != 0
397                                      ? strlen (sponsor) + 1 : 0)
398                                   + ((mask & CEN_REVISION) != 0
399                                      ? strlen (revision) + 1 : 0) + 1
400                                   + strlen (domain) + 1);
401
402       if (filename == NULL)
403         return NULL;
404
405       retval = NULL;
406       last = NULL;
407
408       /* Construct file name.  */
409       cp = stpcpy (filename, dirname);
410       *cp++ = '/';
411       cp = stpcpy (cp, language);
412
413       if ((mask & TERRITORY) != 0)
414         {
415           *cp++ = '_';
416           cp = stpcpy (cp, territory);
417         }
418       if ((mask & XPG_CODESET) != 0)
419         {
420           *cp++ = '.';
421           cp = stpcpy (cp, codeset);
422         }
423       if ((mask & XPG_NORM_CODESET) != 0)
424         {
425           *cp++ = '.';
426           cp = stpcpy (cp, normalized_codeset);
427         }
428       if ((mask & (XPG_MODIFIER | CEN_AUDIENCE)) != 0)
429         {
430           /* This component can be part of both syntaces but has different
431              leading characters.  For CEN we use `+', else `@'.  */
432           *cp++ = (mask & CEN_AUDIENCE) != 0 ? '+' : '@';
433           cp = stpcpy (cp, modifier);
434         }
435       if ((mask & CEN_SPECIAL) != 0)
436         {
437           *cp++ = '+';
438           cp = stpcpy (cp, special);
439         }
440       if ((mask & CEN_SPONSOR) != 0)
441         {
442           *cp++ = ',';
443           cp = stpcpy (cp, sponsor);
444         }
445       if ((mask & CEN_REVISION) != 0)
446         {
447           *cp++ = '_';
448           cp = stpcpy (cp, revision);
449         }
450
451       *cp++ = '/';
452       stpcpy (cp, domain);
453
454       /* Look in list of already loaded domains whether it is already
455          available.  */
456       last = NULL;
457       for (retval = _nl_loaded_domains; retval != NULL; retval = retval->next)
458         if (retval->filename != NULL)
459           {
460             int compare = strcmp (retval->filename, filename);
461             if (compare == 0)
462               /* We found it!  */
463               break;
464             if (compare < 0)
465               {
466                 /* It's not in the list.  */
467                 retval = NULL;
468                 break;
469               }
470
471             last = retval;
472           }
473
474       if (retval != NULL || do_allocate == 0)
475         {
476           free (filename);
477           return retval;
478         }
479     }
480
481   retval = (struct loaded_domain *) malloc (sizeof (*retval));
482   if (retval == NULL)
483     return NULL;
484
485   retval->filename = filename;
486   retval->decided = 0;
487
488   if (last == NULL)
489     {
490       retval->next = _nl_loaded_domains;
491       _nl_loaded_domains = retval;
492     }
493   else
494     {
495       retval->next = last->next;
496       last->next = retval;
497     }
498
499   entries = 0;
500   for (cnt = 254; cnt >= 0; --cnt)
501     if (cnt < mask && (cnt & ~mask) == 0
502         && ((cnt & CEN_SPECIFIC) == 0 || (cnt & XPG_SPECIFIC) == 0)
503         && ((cnt & XPG_CODESET) == 0 || (cnt & XPG_NORM_CODESET) == 0))
504       retval->successor[entries++] = make_entry_rec (dirname, cnt,
505                                                      language, territory,
506                                                      codeset,
507                                                      normalized_codeset,
508                                                      modifier, special,
509                                                      sponsor, revision,
510                                                      domain, 1);
511   retval->successor[entries] = NULL;
512
513   return retval;
514 }
515
516
517 static const char *
518 normalize_codeset (codeset)
519      const char *codeset;
520 {
521   int len = 0;
522   int only_digit = 1;
523   const char *cp;
524   char *retval;
525   char *wp;
526
527   for (cp = codeset; cp[0] != '\0'; ++cp)
528     if (isalnum (cp[0]))
529       {
530         ++len;
531
532         if (isalpha (cp[0]))
533           only_digit = 0;
534       }
535
536   retval = (char *) malloc ((only_digit ? 3 : 0) + len + 1);
537
538   if (retval != NULL)
539     {
540       if (only_digit)
541         wp = stpcpy (retval, "ISO");
542       else
543         wp = retval;
544
545       for (cp = codeset; cp[0] != '\0'; ++cp)
546         if (isalpha (cp[0]))
547           *wp++ = toupper (cp[0]);
548         else if (isdigit (cp[0]))
549           *wp++ = cp[0];
550
551       *wp = '\0';
552     }
553
554   return (const char *) retval;
555 }
556
557
558 /* @@ begin of epilog @@ */
559
560 /* We don't want libintl.a to depend on any other library.  So we
561    avoid the non-standard function stpcpy.  In GNU C Library this
562    function is available, though.  Also allow the symbol HAVE_STPCPY
563    to be defined.  */
564 #if !_LIBC && !HAVE_STPCPY
565 static char *
566 stpcpy__ (dest, src)
567      char *dest;
568      const char *src;
569 {
570   while ((*dest++ = *src++) != '\0')
571     /* Do nothing. */ ;
572   return dest - 1;
573 }
574 #endif