(headers): New variable, libintl.h.
[kopensolaris-gnu/glibc.git] / intl / finddomain.c
1 /* finddomain.c -- handle list of needed message catalogs
2    Copyright (C) 1995 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 __stpcpy
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_CODESET     8
74 #define TERRITORY       16
75 #define CEN_AUDIENCE    32
76 #define XPG_MODIFIER    64
77
78 #define CEN_SPECIFIC    (CEN_REVISION|CEN_SPONSOR|CEN_SPECIAL|CEN_AUDIENCE)
79 #define XPG_SPECIFIC    (XPG_CODESET|XPG_MODIFIER)
80
81
82 /* List of already loaded domains.  */
83 static struct loaded_domain *_nl_loaded_domains;
84
85 /* Prototypes for local functions.  */
86 static struct loaded_domain *make_entry_rec __P ((const char *dirname,
87                                                   int mask,
88                                                   const char *language,
89                                                   const char *territory,
90                                                   const char *codeset,
91                                                   const char *modifier,
92                                                   const char *special,
93                                                   const char *sponsor,
94                                                   const char *revision,
95                                                   const char *domainname,
96                                                   int do_allocate));
97
98 /* Substitution for systems lacking this function in their C library.  */
99 #if !_LIBC && !HAVE_STPCPY
100 static char *stpcpy __P ((char *dest, const char *src));
101 #endif
102
103
104 /* Return a data structure describing the message catalog described by
105    the DOMAINNAME and CATEGORY parameters with respect to the currently
106    established bindings.  */
107 struct loaded_domain *
108 _nl_find_domain (dirname, locale, domainname)
109      const char *dirname;
110      char *locale;
111      const char *domainname;
112 {
113   enum { undecided, xpg, cen } syntax;
114   struct loaded_domain *retval;
115   const char *language;
116   const char *modifier = NULL;
117   const char *territory = NULL;
118   const char *codeset = NULL;
119   const char *special = NULL;
120   const char *sponsor = NULL;
121   const char *revision = NULL;
122   const char *alias_value = NULL;
123   char *cp;
124   int mask;
125
126   /* CATEGORYVALUE now possibly contains a colon separated list of
127      locales.  Each single locale can consist of up to four recognized
128      parts for the XPG syntax:
129
130                 language[_territory[.codeset]][@modifier]
131
132      and six parts for the CEN syntax:
133
134         language[_territory][+audience][+special][,sponsor][_revision]
135
136      Beside the first all of them are allowed to be missing.  If the
137      full specified locale is not found, the less specific one are
138      looked for.  The various part will be stripped of according to
139      the following order:
140                 (1) revision
141                 (2) sponsor
142                 (3) special
143                 (4) codeset
144                 (5) territory
145                 (6) audience/modifier
146    */
147
148   /* If we have already tested for this locale entry there has to
149      be one data set in the list of loaded domains.  */
150   retval = make_entry_rec (dirname, 0, locale, NULL, NULL, NULL,
151                            NULL, NULL, NULL, domainname, 0);
152   if (retval != NULL)
153     {
154       /* We know something about this locale.  */
155       int cnt;
156
157       if (retval->decided == 0)
158         _nl_load_domain (retval); /* @@@ */
159
160       if (retval->data != NULL)
161         return retval;
162
163       for (cnt = 0; retval->successor[cnt] != NULL; ++cnt)
164         {
165           if (retval->successor[cnt]->decided == 0)
166             _nl_load_domain (retval->successor[cnt]);
167
168           if (retval->successor[cnt]->data != NULL)
169             break;
170         }
171
172       /* We really found some usable information.  */
173       return cnt >= 0 ? retval : NULL;
174       /* NOTREACHED */
175     }
176
177   /* See whether the locale value is an alias.  If yes its value
178      *overwrites* the alias name.  No test for the original value is
179      done.  */
180   alias_value = _nl_expand_alias (locale);
181   if (alias_value != NULL)
182     {
183       size_t len = strlen (alias_value) + 1;
184       locale = (char *) malloc (len);
185       if (locale == NULL)
186         return NULL;
187
188       memcpy (locale, alias_value, len);
189     }
190
191   /* Now we determine the single parts of the locale name.  First
192      look for the language.  Termination symbols are `_' and `@' if
193      we use XPG4 style, and `_', `+', and `,' if we use CEN syntax.  */
194   mask = 0;
195   syntax = undecided;
196   language = cp = locale;
197   while (cp[0] != '\0' && cp[0] != '_' && cp[0] != '@'
198          && cp[0] != '+' && cp[0] != ',')
199     ++cp;
200
201   if (language == cp)
202     /* This does not make sense: language has to be specified.  Use
203        this entry as it is without exploding.  Perhaps it is an alias.  */
204     cp = strchr (language, '\0');
205   else if (cp[0] == '_')
206     {
207       /* Next is the territory.  */
208       cp[0] = '\0';
209       territory = ++cp;
210
211       while (cp[0] != '\0' && cp[0] != '.' && cp[0] != '@'
212              && cp[0] != '+' && cp[0] != ',' && cp[0] != '_')
213         ++cp;
214
215       mask |= TERRITORY;
216
217       if (cp[0] == '.')
218         {
219           /* Next is the codeset.  */
220           syntax = xpg;
221           cp[0] = '\0';
222           codeset = ++cp;
223
224           while (cp[0] != '\0' && cp[0] != '@')
225             ++cp;
226
227           mask |= XPG_CODESET;
228         }
229     }
230
231   if (cp[0] == '@' || (syntax != xpg && cp[0] == '+'))
232     {
233       /* Next is the modifier.  */
234       syntax = cp[0] == '@' ? xpg : cen;
235       cp[0] = '\0';
236       modifier = ++cp;
237
238       while (syntax == cen && cp[0] != '\0' && cp[0] != '+'
239              && cp[0] != ',' && cp[0] != '_')
240         ++cp;
241
242       mask |= XPG_MODIFIER | CEN_AUDIENCE;
243     }
244
245   if (syntax != xpg && (cp[0] == '+' || cp[0] == ',' || cp[0] == '_'))
246     {
247       syntax = cen;
248
249       if (cp[0] == '+')
250         {
251           /* Next is special application (CEN syntax).  */
252           cp[0] = '\0';
253           special = ++cp;
254
255           while (cp[0] != '\0' && cp[0] != ',' && cp[0] != '_')
256             ++cp;
257
258           mask |= CEN_SPECIAL;
259         }
260
261       if (cp[0] == ',')
262         {
263           /* Next is sponsor (CEN syntax).  */
264           cp[0] = '\0';
265           sponsor = ++cp;
266
267           while (cp[0] != '\0' && cp[0] != '_')
268             ++cp;
269
270           mask |= CEN_SPONSOR;
271         }
272
273       if (cp[0] == '_')
274         {
275           /* Next is revision (CEN syntax).  */
276           cp[0] = '\0';
277           revision = ++cp;
278
279           mask |= CEN_REVISION;
280         }
281     }
282
283   /* For CEN sytnax values it might be important to have the
284      separator character in the file name, not for XPG syntax.  */
285   if (syntax == xpg)
286     {
287       if (territory != NULL && territory[0] == '\0')
288         mask &= ~TERRITORY;
289
290       if (codeset != NULL && codeset[0] == '\0')
291         mask &= ~XPG_CODESET;
292
293       if (modifier != NULL && modifier[0] == '\0')
294         mask &= ~XPG_MODIFIER;
295     }
296
297   /* Create all possible locale entries which might be interested in
298      generalzation.  */
299   retval = make_entry_rec (dirname, mask, language, territory, codeset,
300                            modifier, special, sponsor, revision,
301                            domainname, 1);
302   if (retval == NULL)
303     /* This means we are out of core.  */
304     return NULL;
305
306   if (retval->decided == 0)
307     _nl_load_domain (retval);
308   if (retval->data == NULL)
309     {
310       int cnt;
311       for (cnt = 0; retval->successor[cnt] != NULL; ++cnt)
312         {
313           if (retval->successor[cnt]->decided == 0)
314             _nl_load_domain (retval->successor[cnt]);
315           if (retval->successor[cnt]->data != NULL)
316             break;
317
318           /* Signal that locale is not available.  */
319           retval->successor[cnt] = NULL;
320         }
321       if (retval->successor[cnt] == NULL)
322         retval = NULL;
323     }
324
325   /* The room for an alias was dynamically allocated.  Free it now.  */
326   if (alias_value != NULL)
327     free (locale);
328
329   return retval;
330 }
331
332
333 static struct loaded_domain *
334 make_entry_rec (dirname, mask, language, territory, codeset, modifier,
335                 special, sponsor, revision, domain, do_allocate)
336      const char *dirname;
337      int mask;
338      const char *language;
339      const char *territory;
340      const char *codeset;
341      const char *modifier;
342      const char *special;
343      const char *sponsor;
344      const char *revision;
345      const char *domain;
346      int do_allocate;
347 {
348   char *filename = NULL;
349   struct loaded_domain *last = NULL;
350   struct loaded_domain *retval;
351   char *cp;
352   size_t entries;
353   int cnt;
354
355
356   /* Process the current entry described by the MASK only when it is
357      valid.  Because the mask can have in the first call bits from
358      both syntaces set this is necessary to prevent constructing
359      illegal local names.  */
360   /* FIXME: Rewrite because test is necessary only in first round.  */
361   if ((mask & CEN_SPECIFIC) == 0 || (mask & XPG_SPECIFIC) == 0)
362     {
363       /* Allocate room for the full file name.  */
364       filename = (char *) malloc (strlen (dirname) + 1
365                                   + strlen (language)
366                                   + ((mask & TERRITORY) != 0
367                                      ? strlen (territory) : 0)
368                                   + ((mask & XPG_CODESET) != 0
369                                      ? strlen (codeset) : 0)
370                                   + ((mask & XPG_MODIFIER) != 0 ?
371                                      strlen (modifier) : 0)
372                                   + ((mask & CEN_SPECIAL) != 0
373                                      ? strlen (special) : 0)
374                                   + ((mask & CEN_SPONSOR) != 0
375                                      ? strlen (sponsor) : 0)
376                                   + ((mask & CEN_REVISION) != 0
377                                      ? strlen (revision) : 0) + 1
378                                   + strlen (domain) + 1);
379
380       if (filename == NULL)
381         return NULL;
382
383       retval = NULL;
384       last = NULL;
385
386       /* Construct file name.  */
387       cp = stpcpy (filename, dirname);
388       *cp++ = '/';
389       cp = stpcpy (cp, language);
390
391       if ((mask & TERRITORY) != 0)
392         {
393           *cp++ = '_';
394           cp = stpcpy (cp, territory);
395         }
396       if ((mask & XPG_CODESET) != 0)
397         {
398           *cp++ = '.';
399       cp = stpcpy (cp, codeset);
400         }
401       if ((mask & (XPG_MODIFIER | CEN_AUDIENCE)) != 0)
402         {
403           /* This component can be part of both syntaces but has different
404              leading characters.  For CEN we use `+', else `@'.  */
405           *cp++ = (mask & CEN_AUDIENCE) != 0 ? '+' : '@';
406           cp = stpcpy (cp, modifier);
407         }
408       if ((mask & CEN_SPECIAL) != 0)
409         {
410           *cp++ = '+';
411           cp = stpcpy (cp, special);
412         }
413       if ((mask & CEN_SPONSOR) != 0)
414         {
415           *cp++ = ',';
416           cp = stpcpy (cp, sponsor);
417         }
418       if ((mask & CEN_REVISION) != 0)
419         {
420           *cp++ = '_';
421           cp = stpcpy (cp, revision);
422         }
423
424       *cp++ = '/';
425       stpcpy (cp, domain);
426
427       /* Look in list of already loaded domains whether it is already
428          available.  */
429       last = NULL;
430       for (retval = _nl_loaded_domains; retval != NULL; retval = retval->next)
431         if (retval->filename != NULL)
432           {
433             int compare = strcmp (retval->filename, filename);
434             if (compare == 0)
435               /* We found it!  */
436               break;
437             if (compare < 0)
438               {
439                 /* It's not in the list.  */
440                 retval = NULL;
441                 break;
442               }
443
444             last = retval;
445           }
446
447       if (retval != NULL || do_allocate == 0)
448         {
449           free (filename);
450           return retval;
451         }
452     }
453
454   retval = (struct loaded_domain *) malloc (sizeof (*retval));
455   if (retval == NULL)
456     return NULL;
457
458   retval->filename = filename;
459   retval->decided = 0;
460
461   if (last == NULL)
462     {
463       retval->next = _nl_loaded_domains;
464       _nl_loaded_domains = retval;
465     }
466   else
467     {
468       retval->next = last->next;
469       last->next = retval;
470     }
471
472   entries = 0;
473   for (cnt = 126; cnt >= 0; --cnt)
474     if (cnt < mask && (cnt & ~mask) == 0
475         && ((cnt & CEN_SPECIFIC) == 0 || (cnt & XPG_SPECIFIC) == 0))
476       retval->successor[entries++] = make_entry_rec (dirname, cnt,
477                                                      language, territory,
478                                                      codeset, modifier,
479                                                      special, sponsor,
480                                                      revision, domain, 1);
481   retval->successor[entries] = NULL;
482
483   return retval;
484 }
485
486
487 /* @@ begin of epilog @@ */
488
489 /* We don't want libintl.a to depend on any other library.  So we
490    avoid the non-standard function stpcpy.  In GNU C Library this
491    function is available, though.  Also allow the symbol HAVE_STPCPY
492    to be defined.  */
493 #if !_LIBC && !HAVE_STPCPY
494 static char *
495 stpcpy (dest, src)
496      char *dest;
497      const char *src;
498 {
499   while ((*dest++ = *src++) != '\0')
500     /* Do nothing. */ ;
501   return dest - 1;
502 }
503 #endif