Update.
[platform/upstream/glibc.git] / locale / programs / locale.c
1 /* Implementation of the locale program according to POSIX 9945-2.
2    Copyright (C) 1995-1997, 1999, 2000, 2001 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1995.
5
6    The GNU C Library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public License as
8    published by the Free Software Foundation; either version 2 of the
9    License, or (at your option) any later version.
10
11    The GNU C Library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15
16    You should have received a copy of the GNU Library General Public
17    License along with the GNU C Library; see the file COPYING.LIB.  If not,
18    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19    Boston, MA 02111-1307, USA.  */
20
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24
25 #include <argp.h>
26 #include <argz.h>
27 #include <dirent.h>
28 #include <errno.h>
29 #include <error.h>
30 #include <fcntl.h>
31 #include <langinfo.h>
32 #include <libintl.h>
33 #include <limits.h>
34 #include <locale.h>
35 #include <search.h>
36 #include <stdio.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <unistd.h>
40 #include <sys/mman.h>
41 #include <sys/stat.h>
42
43 #include "localeinfo.h"
44 #include "charmap-dir.h"
45
46 extern void *xmalloc (size_t __n);
47 extern char *xstrdup (const char *__str);
48
49
50 /* If set print the name of the category.  */
51 static int show_category_name;
52
53 /* If set print the name of the item.  */
54 static int show_keyword_name;
55
56 /* Print names of all available locales.  */
57 static int do_all;
58
59 /* Print names of all available character maps.  */
60 static int do_charmaps = 0;
61
62 /* Nonzero if verbose output is wanted.  */
63 static int verbose;
64
65 /* Name and version of program.  */
66 static void print_version (FILE *stream, struct argp_state *state);
67 void (*argp_program_version_hook) (FILE *, struct argp_state *) = print_version;
68
69 /* Definitions of arguments for argp functions.  */
70 static const struct argp_option options[] =
71 {
72   { NULL, 0, NULL, 0, N_("System information:") },
73   { "all-locales", 'a', NULL, OPTION_NO_USAGE,
74     N_("Write names of available locales") },
75   { "charmaps", 'm', NULL, OPTION_NO_USAGE,
76     N_("Write names of available charmaps") },
77   { NULL, 0, NULL, 0, N_("Modify output format:") },
78   { "category-name", 'c', NULL, 0, N_("Write names of selected categories") },
79   { "keyword-name", 'k', NULL, 0, N_("Write names of selected keywords") },
80   { "verbose", 'v', NULL, 0, N_("Print more information") },
81   { NULL, 0, NULL, 0, NULL }
82 };
83
84 /* Short description of program.  */
85 static const char doc[] = N_("Get locale-specific information.");
86
87 /* Strings for arguments in help texts.  */
88 static const char args_doc[] = N_("NAME\n[-a|-m]");
89
90 /* Prototype for option handler.  */
91 static error_t parse_opt (int key, char *arg, struct argp_state *state);
92
93 /* Function to print some extra text in the help message.  */
94 static char *more_help (int key, const char *text, void *input);
95
96 /* Data structure to communicate with argp functions.  */
97 static struct argp argp =
98 {
99   options, parse_opt, args_doc, doc, NULL, more_help
100 };
101
102
103 /* We don't have these constants defined because we don't use them.  Give
104    default values.  */
105 #define CTYPE_MB_CUR_MIN 0
106 #define CTYPE_MB_CUR_MAX 0
107 #define CTYPE_HASH_SIZE 0
108 #define CTYPE_HASH_LAYERS 0
109 #define CTYPE_CLASS 0
110 #define CTYPE_TOUPPER_EB 0
111 #define CTYPE_TOLOWER_EB 0
112 #define CTYPE_TOUPPER_EL 0
113 #define CTYPE_TOLOWER_EL 0
114
115 /* Definition of the data structure which represents a category and its
116    items.  */
117 struct category
118 {
119   int cat_id;
120   const char *name;
121   size_t number;
122   struct cat_item
123   {
124     int item_id;
125     const char *name;
126     enum { std, opt } status;
127     enum value_type value_type;
128     int min;
129     int max;
130   } *item_desc;
131 };
132
133 /* Simple helper macro.  */
134 #define NELEMS(arr) ((sizeof (arr)) / (sizeof (arr[0])))
135
136 /* For some tricky stuff.  */
137 #define NO_PAREN(Item, More...) Item, ## More
138
139 /* We have all categories defined in `categories.def'.  Now construct
140    the description and data structure used for all categories.  */
141 #define DEFINE_ELEMENT(Item, More...) { Item, ## More },
142 #define DEFINE_CATEGORY(category, name, items, postload) \
143     static struct cat_item category##_desc[] =                                \
144       {                                                                       \
145         NO_PAREN items                                                        \
146       };
147
148 #include "categories.def"
149 #undef DEFINE_CATEGORY
150
151 static struct category category[] =
152   {
153 #define DEFINE_CATEGORY(category, name, items, postload) \
154     [category] = { _NL_NUM_##category, name, NELEMS (category##_desc),        \
155                    category##_desc },
156 #include "categories.def"
157 #undef DEFINE_CATEGORY
158   };
159 #define NCATEGORIES NELEMS (category)
160
161
162 /* Automatically set variable.  */
163 extern const char *__progname;
164
165 /* helper function for extended name handling.  */
166 extern void locale_special (const char *name, int show_category_name,
167                             int show_keyword_name);
168
169 /* Prototypes for local functions.  */
170 static void write_locales (void);
171 static void write_charmaps (void);
172 static void show_locale_vars (void);
173 static void show_info (const char *name);
174
175
176 int
177 main (int argc, char *argv[])
178 {
179   int remaining;
180
181   /* Set initial values for global variables.  */
182   show_category_name = 0;
183   show_keyword_name = 0;
184
185   /* Set locale.  Do not set LC_ALL because the other categories must
186      not be affected (according to POSIX.2).  */
187   setlocale (LC_CTYPE, "");
188   setlocale (LC_MESSAGES, "");
189
190   /* Initialize the message catalog.  */
191   textdomain (PACKAGE);
192
193   /* Parse and process arguments.  */
194   argp_parse (&argp, argc, argv, 0, &remaining, NULL);
195
196   /* `-a' requests the names of all available locales.  */
197   if (do_all != 0)
198     {
199       setlocale (LC_COLLATE, "");
200       write_locales ();
201       exit (EXIT_SUCCESS);
202     }
203
204   /* `m' requests the names of all available charmaps.  The names can be
205      used for the -f argument to localedef(1).  */
206   if (do_charmaps != 0)
207     {
208       write_charmaps ();
209       exit (EXIT_SUCCESS);
210     }
211
212   /* Specific information about the current locale are requested.
213      Change to this locale now.  */
214   setlocale (LC_ALL, "");
215
216   /* If no real argument is given we have to print the contents of the
217      current locale definition variables.  These are LANG and the LC_*.  */
218   if (remaining == argc && show_keyword_name == 0 && show_category_name == 0)
219     {
220       show_locale_vars ();
221       exit (EXIT_SUCCESS);
222     }
223
224   /* Process all given names.  */
225   while (remaining <  argc)
226     show_info (argv[remaining++]);
227
228   exit (EXIT_SUCCESS);
229 }
230
231
232 /* Handle program arguments.  */
233 static error_t
234 parse_opt (int key, char *arg, struct argp_state *state)
235 {
236   switch (key)
237     {
238     case 'a':
239       do_all = 1;
240       break;
241     case 'c':
242       show_category_name = 1;
243       break;
244     case 'm':
245       do_charmaps = 1;
246       break;
247     case 'k':
248       show_keyword_name = 1;
249       break;
250     case 'v':
251       verbose = 1;
252       break;
253     default:
254       return ARGP_ERR_UNKNOWN;
255     }
256   return 0;
257 }
258
259
260 static char *
261 more_help (int key, const char *text, void *input)
262 {
263   switch (key)
264     {
265     case ARGP_KEY_HELP_EXTRA:
266       /* We print some extra information.  */
267       return xstrdup (gettext ("\
268 Report bugs using the `glibcbug' script to <bugs@gnu.org>.\n"));
269     default:
270       break;
271     }
272   return (char *) text;
273 }
274
275 /* Print the version information.  */
276 static void
277 print_version (FILE *stream, struct argp_state *state)
278 {
279   fprintf (stream, "locale (GNU %s) %s\n", PACKAGE, VERSION);
280   fprintf (stream, gettext ("\
281 Copyright (C) %s Free Software Foundation, Inc.\n\
282 This is free software; see the source for copying conditions.  There is NO\n\
283 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
284 "), "2001");
285   fprintf (stream, gettext ("Written by %s.\n"), "Ulrich Drepper");
286 }
287
288
289 /* Simple action function which prints arguments as strings.  */
290 static void
291 print_names (const void *nodep, VISIT value, int level)
292 {
293   if (value == postorder || value == leaf)
294     puts (*(char **) nodep);
295 }
296
297
298 /* Write the names of all available locales to stdout.  We have some
299    sources of the information: the contents of the locale directory
300    and the locale.alias file.  To avoid duplicates and print the
301    result is a reasonable order we put all entries is a search tree
302    and print them afterwards.  */
303 static void
304 write_locales (void)
305 {
306   char linebuf[80];
307   void *all_data = NULL;
308   DIR *dir;
309   struct dirent *dirent;
310   char *alias_path;
311   size_t alias_path_len;
312   char *entry;
313   int first_locale = 1;
314
315 #define PUT(name) tsearch (name, &all_data, \
316                            (int (*) (const void *, const void *)) strcoll)
317
318   /* Now read the locale.alias files.  */
319   if (argz_create_sep (LOCALE_ALIAS_PATH, ':', &alias_path, &alias_path_len))
320     error (1, errno, gettext ("while preparing output"));
321
322   entry = NULL;
323   while ((entry = argz_next (alias_path, alias_path_len, entry)))
324     {
325       static const char aliasfile[] = "/locale.alias";
326       FILE *fp;
327       char full_name[strlen (entry) + sizeof aliasfile];
328
329       stpcpy (stpcpy (full_name, entry), aliasfile);
330       fp = fopen (full_name, "r");
331       if (fp == NULL)
332         /* Ignore non-existing files.  */
333         continue;
334
335       while (! feof (fp))
336         {
337           /* It is a reasonable approach to use a fix buffer here
338              because
339              a) we are only interested in the first two fields
340              b) these fields must be usable as file names and so must
341                 not be that long  */
342           char buf[BUFSIZ];
343           char *alias;
344           char *value;
345           char *cp;
346
347           if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
348             /* EOF reached.  */
349             break;
350
351           cp = buf;
352           /* Ignore leading white space.  */
353           while (isspace (cp[0]) && cp[0] != '\n')
354             ++cp;
355
356           /* A leading '#' signals a comment line.  */
357           if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
358             {
359               alias = cp++;
360               while (cp[0] != '\0' && !isspace (cp[0]))
361                 ++cp;
362               /* Terminate alias name.  */
363               if (cp[0] != '\0')
364                 *cp++ = '\0';
365
366               /* Now look for the beginning of the value.  */
367               while (isspace (cp[0]))
368                 ++cp;
369
370               if (cp[0] != '\0')
371                 {
372                   value = cp++;
373                   while (cp[0] != '\0' && !isspace (cp[0]))
374                     ++cp;
375                   /* Terminate value.  */
376                   if (cp[0] == '\n')
377                     {
378                       /* This has to be done to make the following
379                          test for the end of line possible.  We are
380                          looking for the terminating '\n' which do not
381                          overwrite here.  */
382                       *cp++ = '\0';
383                       *cp = '\n';
384                     }
385                   else if (cp[0] != '\0')
386                     *cp++ = '\0';
387
388                   /* Add the alias.  */
389                   if (! verbose)
390                     PUT (xstrdup (alias));
391                 }
392             }
393
394           /* Possibly not the whole line fits into the buffer.
395              Ignore the rest of the line.  */
396           while (strchr (cp, '\n') == NULL)
397             {
398               cp = buf;
399               if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
400                 /* Make sure the inner loop will be left.  The outer
401                    loop will exit at the `feof' test.  */
402                 *cp = '\n';
403             }
404         }
405
406       fclose (fp);
407     }
408
409   /* This is the directory with the locale files.  */
410   dir = opendir (LOCALEDIR);
411   if (dir == NULL)
412     {
413       error (1, errno, gettext ("cannot read locale directory `%s'"),
414              LOCALEDIR);
415       return;
416     }
417
418   memset (linebuf, '-', sizeof (linebuf) - 1);
419   linebuf[sizeof (linebuf) - 1] = '\0';
420
421   /* Now we can look for all files in the directory.  */
422   while ((dirent = readdir (dir)) != NULL)
423     if (strcmp (dirent->d_name, ".") != 0
424         && strcmp (dirent->d_name, "..") != 0)
425       {
426         mode_t mode;
427 #ifdef _DIRENT_HAVE_D_TYPE
428         if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK)
429           mode = DTTOIF (dirent->d_type);
430         else
431 #endif
432           {
433             struct stat64 st;
434             char buf[sizeof (LOCALEDIR) + strlen (dirent->d_name) + 1];
435
436             stpcpy (stpcpy (stpcpy (buf, LOCALEDIR), "/"), dirent->d_name);
437
438             if (stat64 (buf, &st) < 0)
439               continue;
440             mode = st.st_mode;
441           }
442
443         if (S_ISDIR (mode))
444           {
445             /* Test whether at least the LC_CTYPE data is there.  Some
446                directories only contain translations.  */
447             char buf[sizeof (LOCALEDIR) + strlen (dirent->d_name)
448                     + sizeof "/LC_IDENTIFICATION"];
449             char *enddir;
450             struct stat64 st;
451
452             stpcpy (enddir = stpcpy (stpcpy (stpcpy (buf, LOCALEDIR), "/"),
453                                      dirent->d_name),
454                     "/LC_IDENTIFICATION");
455
456             if (stat64 (buf, &st) == 0 && S_ISREG (st.st_mode))
457               {
458                 if (verbose)
459                   {
460                     /* Provide some nice output of all kinds of
461                        information.  */
462                     int fd;
463
464                     if (! first_locale)
465                       putchar_unlocked ('\n');
466                     first_locale = 0;
467
468                     printf ("locale: %-15.15s directory: %.*s\n%s\n",
469                             dirent->d_name, (int) (enddir - buf), buf,
470                             linebuf);
471
472                     fd = open64 (buf, O_RDONLY);
473                     if (fd != -1)
474                       {
475                         void *mapped = mmap64 (NULL, st.st_size, PROT_READ,
476                                                MAP_SHARED, fd, 0);
477                         if (mapped != MAP_FAILED)
478                           {
479                             /* Read the information from the file.  */
480                             struct
481                             {
482                               unsigned int magic;
483                               unsigned int nstrings;
484                               unsigned int strindex[0];
485                             } *filedata = mapped;
486
487                             if (filedata->magic == LIMAGIC (LC_IDENTIFICATION)
488                                 && (sizeof *filedata
489                                     + (filedata->nstrings
490                                        * sizeof (unsigned int))
491                                     <= (size_t) st.st_size))
492                               {
493                                 const char *str;
494
495 #define HANDLE(idx, name) \
496   str = ((char *) mapped                                                      \
497          + filedata->strindex[_NL_ITEM_INDEX (_NL_IDENTIFICATION_##idx)]);    \
498   if (*str != '\0')                                                           \
499     printf ("%9s | %s\n", name, str)
500                                 HANDLE (TITLE, "title");
501                                 HANDLE (SOURCE, "source");
502                                 HANDLE (ADDRESS, "address");
503                                 HANDLE (CONTACT, "contact");
504                                 HANDLE (EMAIL, "email");
505                                 HANDLE (TEL, "telephone");
506                                 HANDLE (FAX, "fax");
507                                 HANDLE (LANGUAGE, "language");
508                                 HANDLE (TERRITORY, "territory");
509                                 HANDLE (AUDIENCE, "audience");
510                                 HANDLE (APPLICATION, "application");
511                                 HANDLE (ABBREVIATION, "abbreviation");
512                                 HANDLE (REVISION, "revision");
513                                 HANDLE (DATE, "date");
514                               }
515
516                             munmap (mapped, st.st_size);
517                           }
518
519                         close (fd);
520
521                         /* Now try to get the charset information.  */
522                         strcpy (enddir, "/LC_CTYPE");
523                         fd = open64 (buf, O_RDONLY);
524                         if (fd != -1 && fstat64 (fd, &st) >= 0
525                             && ((mapped = mmap64 (NULL, st.st_size, PROT_READ,
526                                                   MAP_SHARED, fd, 0))
527                                 != MAP_FAILED))
528                           {
529                             struct
530                             {
531                               unsigned int magic;
532                               unsigned int nstrings;
533                               unsigned int strindex[0];
534                             } *filedata = mapped;
535
536                             if (filedata->magic == LIMAGIC (LC_CTYPE)
537                                 && (sizeof *filedata
538                                     + (filedata->nstrings
539                                        * sizeof (unsigned int))
540                                     <= (size_t) st.st_size))
541                               {
542                                 const char *str;
543
544                                 str = ((char *) mapped
545                                        + filedata->strindex[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME)]);
546                                 if (*str != '\0')
547                                   printf ("  codeset | %s\n", str);
548                               }
549
550                             munmap (mapped, st.st_size);
551                           }
552
553                         if (fd != -1)
554                           close (fd);
555                       }
556                   }
557                 else
558                   /* If the verbose format is not selected we simply
559                      collect the names.  */
560                   PUT (xstrdup (dirent->d_name));
561               }
562           }
563       }
564
565   closedir (dir);
566
567   if (! verbose)
568     {
569       /* `POSIX' locale is always available (POSIX.2 4.34.3).  */
570       PUT ("POSIX");
571       /* And so is the "C" locale.  */
572       PUT ("C");
573
574       twalk (all_data, print_names);
575     }
576 }
577
578
579 /* Write the names of all available character maps to stdout.  */
580 static void
581 write_charmaps (void)
582 {
583   void *all_data = NULL;
584   CHARMAP_DIR *dir;
585   const char *dirent;
586
587   /* Look for all files in the charmap directory.  */
588   dir = charmap_opendir (CHARMAP_PATH);
589   if (dir == NULL)
590     return;
591
592   while ((dirent = charmap_readdir (dir)) != NULL)
593     {
594       char **aliases;
595       char **p;
596
597       PUT (xstrdup (dirent));
598
599       aliases = charmap_aliases (CHARMAP_PATH, dirent);
600
601 #if 0
602       /* Add the code_set_name and the aliases.  */
603       for (p = aliases; *p; p++)
604         PUT (xstrdup (*p));
605 #else
606       /* Add the code_set_name only.  Most aliases are obsolete.  */
607       p = aliases;
608       if (*p)
609         PUT (xstrdup (*p));
610 #endif
611
612       charmap_free_aliases (aliases);
613     }
614
615   charmap_closedir (dir);
616
617   twalk (all_data, print_names);
618 }
619
620
621 /* We have to show the contents of the environments determining the
622    locale.  */
623 static void
624 show_locale_vars (void)
625 {
626   size_t cat_no;
627   const char *lcall = getenv ("LC_ALL");
628   const char *lang = getenv ("LANG") ? : "POSIX";
629
630   auto void get_source (const char *name);
631
632   void get_source (const char *name)
633     {
634       char *val = getenv (name);
635
636       if ((lcall ?: "")[0] != '\0' || val == NULL)
637         printf ("%s=\"%s\"\n", name, (lcall ?: "")[0] ? lcall : lang);
638       else
639         printf ("%s=%s\n", name, val);
640     }
641
642   /* LANG has to be the first value.  */
643   printf ("LANG=%s\n", lang);
644
645   /* Now all categories in an unspecified order.  */
646   for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
647     if (cat_no != LC_ALL)
648       get_source (category[cat_no].name);
649
650   /* The last is the LC_ALL value.  */
651   printf ("LC_ALL=%s\n", lcall ? : "");
652 }
653
654
655 /* Show the information request for NAME.  */
656 static void
657 show_info (const char *name)
658 {
659   size_t cat_no;
660
661   auto void print_item (struct cat_item *item);
662
663   void print_item (struct cat_item *item)
664     {
665       switch (item->value_type)
666         {
667         case string:
668           if (show_keyword_name)
669             printf ("%s=\"", item->name);
670           fputs (nl_langinfo (item->item_id) ? : "", stdout);
671           if (show_keyword_name)
672             putchar ('"');
673           putchar ('\n');
674           break;
675         case stringarray:
676           {
677             int cnt;
678             const char *val;
679
680             if (show_keyword_name)
681               printf ("%s=\"", item->name);
682
683             for (cnt = 0; cnt < item->max - 1; ++cnt)
684               {
685                 val = nl_langinfo (item->item_id + cnt);
686                 if (val != NULL)
687                   fputs (val, stdout);
688                 putchar (';');
689               }
690
691             val = nl_langinfo (item->item_id + cnt);
692             if (val != NULL)
693               fputs (val, stdout);
694
695             if (show_keyword_name)
696               putchar ('"');
697             putchar ('\n');
698           }
699           break;
700         case stringlist:
701           {
702             int first = 1;
703             const char *val = nl_langinfo (item->item_id) ? : "";
704             int cnt;
705
706             if (show_keyword_name)
707               printf ("%s=", item->name);
708
709             for (cnt = 0; cnt < item->max && *val != '\0'; ++cnt)
710               {
711                 printf ("%s%s%s%s", first ? "" : ";",
712                         show_keyword_name ? "\"" : "", val,
713                         show_keyword_name ? "\"" : "");
714                 val = strchr (val, '\0') + 1;
715                 first = 0;
716               }
717             putchar ('\n');
718           }
719           break;
720         case byte:
721           {
722             const char *val = nl_langinfo (item->item_id);
723
724             if (show_keyword_name)
725               printf ("%s=", item->name);
726
727             if (val != NULL)
728               printf ("%d", *val == CHAR_MAX ? -1 : *val);
729             putchar ('\n');
730           }
731           break;
732         case bytearray:
733           {
734             const char *val = nl_langinfo (item->item_id);
735             int cnt = val ? strlen (val) : 0;
736
737             if (show_keyword_name)
738               printf ("%s=", item->name);
739
740             while (cnt > 1)
741               {
742                 printf ("%d;", *val == CHAR_MAX ? -1 : *val);
743                 --cnt;
744                 ++val;
745               }
746
747             printf ("%d\n", cnt == 0 || *val == CHAR_MAX ? -1 : *val);
748           }
749           break;
750         case word:
751           {
752             unsigned int val =
753               (unsigned int) (unsigned long int) nl_langinfo (item->item_id);
754             if (show_keyword_name)
755               printf ("%s=", item->name);
756
757             printf ("%d\n", val);
758           }
759           break;
760         case wstring:
761         case wstringarray:
762         case wstringlist:
763           /* We don't print wide character information since the same
764              information is available in a multibyte string.  */
765         default:
766           break;
767
768         }
769     }
770
771   for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
772     if (cat_no != LC_ALL)
773       {
774         size_t item_no;
775
776         if (strcmp (name, category[cat_no].name) == 0)
777           /* Print the whole category.  */
778           {
779             if (show_category_name != 0)
780               puts (category[cat_no].name);
781
782             for (item_no = 0; item_no < category[cat_no].number; ++item_no)
783               print_item (&category[cat_no].item_desc[item_no]);
784
785             return;
786           }
787
788         for (item_no = 0; item_no < category[cat_no].number; ++item_no)
789           if (strcmp (name, category[cat_no].item_desc[item_no].name) == 0)
790             {
791               if (show_category_name != 0)
792                 puts (category[cat_no].name);
793
794               print_item (&category[cat_no].item_desc[item_no]);
795               return;
796             }
797       }
798
799   /* The name is not a standard one.
800      For testing and perhaps advanced use allow some more symbols.  */
801   locale_special (name, show_category_name, show_keyword_name);
802 }