9c689d2eb68b830aa77f765c8d320b224be1fab8
[kopensolaris-gnu/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 Lesser General Public
8    License as published by the Free Software Foundation; either
9    version 2.1 of the 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    Lesser General Public License for more details.
15
16    You should have received a copy of the GNU Lesser General Public
17    License along with the GNU C Library; if not, write to the Free
18    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19    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 static int
299 select_dirs (const struct dirent *dirent)
300 {
301   int result = 0;
302
303   if (strcmp (dirent->d_name, ".") != 0 && strcmp (dirent->d_name, "..") != 0)
304     {
305       mode_t mode = 0;
306
307 #ifdef _DIRENT_HAVE_D_TYPE
308       if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK)
309         mode = DTTOIF (dirent->d_type);
310       else
311 #endif
312         {
313           struct stat64 st;
314           char buf[sizeof (LOCALEDIR) + strlen (dirent->d_name) + 1];
315
316           stpcpy (stpcpy (stpcpy (buf, LOCALEDIR), "/"), dirent->d_name);
317
318           if (stat64 (buf, &st) == 0)
319             mode = st.st_mode;
320         }
321
322       result = S_ISDIR (mode);
323     }
324
325   return result;
326 }
327
328
329 /* Write the names of all available locales to stdout.  We have some
330    sources of the information: the contents of the locale directory
331    and the locale.alias file.  To avoid duplicates and print the
332    result is a reasonable order we put all entries is a search tree
333    and print them afterwards.  */
334 static void
335 write_locales (void)
336 {
337   char linebuf[80];
338   void *all_data = NULL;
339   struct dirent **dirents;
340   int ndirents;
341   int cnt;
342   char *alias_path;
343   size_t alias_path_len;
344   char *entry;
345   int first_locale = 1;
346
347 #define PUT(name) tsearch (name, &all_data, \
348                            (int (*) (const void *, const void *)) strcoll)
349
350   /* Now read the locale.alias files.  */
351   if (argz_create_sep (LOCALE_ALIAS_PATH, ':', &alias_path, &alias_path_len))
352     error (1, errno, gettext ("while preparing output"));
353
354   entry = NULL;
355   while ((entry = argz_next (alias_path, alias_path_len, entry)))
356     {
357       static const char aliasfile[] = "/locale.alias";
358       FILE *fp;
359       char full_name[strlen (entry) + sizeof aliasfile];
360
361       stpcpy (stpcpy (full_name, entry), aliasfile);
362       fp = fopen (full_name, "r");
363       if (fp == NULL)
364         /* Ignore non-existing files.  */
365         continue;
366
367       while (! feof (fp))
368         {
369           /* It is a reasonable approach to use a fix buffer here
370              because
371              a) we are only interested in the first two fields
372              b) these fields must be usable as file names and so must
373                 not be that long  */
374           char buf[BUFSIZ];
375           char *alias;
376           char *value;
377           char *cp;
378
379           if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
380             /* EOF reached.  */
381             break;
382
383           cp = buf;
384           /* Ignore leading white space.  */
385           while (isspace (cp[0]) && cp[0] != '\n')
386             ++cp;
387
388           /* A leading '#' signals a comment line.  */
389           if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
390             {
391               alias = cp++;
392               while (cp[0] != '\0' && !isspace (cp[0]))
393                 ++cp;
394               /* Terminate alias name.  */
395               if (cp[0] != '\0')
396                 *cp++ = '\0';
397
398               /* Now look for the beginning of the value.  */
399               while (isspace (cp[0]))
400                 ++cp;
401
402               if (cp[0] != '\0')
403                 {
404                   value = cp++;
405                   while (cp[0] != '\0' && !isspace (cp[0]))
406                     ++cp;
407                   /* Terminate value.  */
408                   if (cp[0] == '\n')
409                     {
410                       /* This has to be done to make the following
411                          test for the end of line possible.  We are
412                          looking for the terminating '\n' which do not
413                          overwrite here.  */
414                       *cp++ = '\0';
415                       *cp = '\n';
416                     }
417                   else if (cp[0] != '\0')
418                     *cp++ = '\0';
419
420                   /* Add the alias.  */
421                   if (! verbose)
422                     PUT (xstrdup (alias));
423                 }
424             }
425
426           /* Possibly not the whole line fits into the buffer.
427              Ignore the rest of the line.  */
428           while (strchr (cp, '\n') == NULL)
429             {
430               cp = buf;
431               if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
432                 /* Make sure the inner loop will be left.  The outer
433                    loop will exit at the `feof' test.  */
434                 *cp = '\n';
435             }
436         }
437
438       fclose (fp);
439     }
440
441   memset (linebuf, '-', sizeof (linebuf) - 1);
442   linebuf[sizeof (linebuf) - 1] = '\0';
443
444   /* Now we can look for all files in the directory.  */
445   ndirents = scandir (LOCALEDIR, &dirents, select_dirs, alphasort);
446   for (cnt = 0; cnt < ndirents; ++cnt)
447     {
448       /* Test whether at least the LC_CTYPE data is there.  Some
449          directories only contain translations.  */
450       char buf[sizeof (LOCALEDIR) + strlen (dirents[cnt]->d_name)
451               + sizeof "/LC_IDENTIFICATION"];
452       char *enddir;
453       struct stat64 st;
454
455       stpcpy (enddir = stpcpy (stpcpy (stpcpy (buf, LOCALEDIR), "/"),
456                                dirents[cnt]->d_name),
457               "/LC_IDENTIFICATION");
458
459       if (stat64 (buf, &st) == 0 && S_ISREG (st.st_mode))
460         {
461           if (verbose)
462             {
463               /* Provide some nice output of all kinds of
464                  information.  */
465               int fd;
466
467               if (! first_locale)
468                 putchar_unlocked ('\n');
469               first_locale = 0;
470
471               printf ("locale: %-15.15s directory: %.*s\n%s\n",
472                       dirents[cnt]->d_name, (int) (enddir - buf), buf,
473                       linebuf);
474
475               fd = open64 (buf, O_RDONLY);
476               if (fd != -1)
477                 {
478                   void *mapped = mmap64 (NULL, st.st_size, PROT_READ,
479                                          MAP_SHARED, fd, 0);
480                   if (mapped != MAP_FAILED)
481                     {
482                       /* Read the information from the file.  */
483                       struct
484                       {
485                         unsigned int magic;
486                         unsigned int nstrings;
487                         unsigned int strindex[0];
488                       } *filedata = mapped;
489
490                       if (filedata->magic == LIMAGIC (LC_IDENTIFICATION)
491                           && (sizeof *filedata
492                               + (filedata->nstrings
493                                  * sizeof (unsigned int))
494                               <= (size_t) st.st_size))
495                         {
496                           const char *str;
497
498 #define HANDLE(idx, name) \
499   str = ((char *) mapped                                                      \
500          + filedata->strindex[_NL_ITEM_INDEX (_NL_IDENTIFICATION_##idx)]);    \
501   if (*str != '\0')                                                           \
502     printf ("%9s | %s\n", name, str)
503                           HANDLE (TITLE, "title");
504                           HANDLE (SOURCE, "source");
505                           HANDLE (ADDRESS, "address");
506                           HANDLE (CONTACT, "contact");
507                           HANDLE (EMAIL, "email");
508                           HANDLE (TEL, "telephone");
509                           HANDLE (FAX, "fax");
510                           HANDLE (LANGUAGE, "language");
511                           HANDLE (TERRITORY, "territory");
512                           HANDLE (AUDIENCE, "audience");
513                           HANDLE (APPLICATION, "application");
514                           HANDLE (ABBREVIATION, "abbreviation");
515                           HANDLE (REVISION, "revision");
516                           HANDLE (DATE, "date");
517                         }
518
519                       munmap (mapped, st.st_size);
520                     }
521
522                   close (fd);
523
524                   /* Now try to get the charset information.  */
525                   strcpy (enddir, "/LC_CTYPE");
526                   fd = open64 (buf, O_RDONLY);
527                   if (fd != -1 && fstat64 (fd, &st) >= 0
528                       && ((mapped = mmap64 (NULL, st.st_size, PROT_READ,
529                                             MAP_SHARED, fd, 0))
530                           != MAP_FAILED))
531                     {
532                       struct
533                       {
534                         unsigned int magic;
535                         unsigned int nstrings;
536                         unsigned int strindex[0];
537                       } *filedata = mapped;
538
539                       if (filedata->magic == LIMAGIC (LC_CTYPE)
540                           && (sizeof *filedata
541                               + (filedata->nstrings
542                                  * sizeof (unsigned int))
543                               <= (size_t) st.st_size))
544                         {
545                           const char *str;
546
547                           str = ((char *) mapped
548                                  + filedata->strindex[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME)]);
549                           if (*str != '\0')
550                             printf ("  codeset | %s\n", str);
551                         }
552
553                       munmap (mapped, st.st_size);
554                     }
555
556                   if (fd != -1)
557                     close (fd);
558                 }
559             }
560           else
561             /* If the verbose format is not selected we simply
562                collect the names.  */
563             PUT (xstrdup (dirents[cnt]->d_name));
564         }
565     }
566   if (ndirents > 0)
567     free (dirents);
568
569   if (! verbose)
570     {
571       /* `POSIX' locale is always available (POSIX.2 4.34.3).  */
572       PUT ("POSIX");
573       /* And so is the "C" locale.  */
574       PUT ("C");
575
576       twalk (all_data, print_names);
577     }
578 }
579
580
581 /* Write the names of all available character maps to stdout.  */
582 static void
583 write_charmaps (void)
584 {
585   void *all_data = NULL;
586   CHARMAP_DIR *dir;
587   const char *dirent;
588
589   /* Look for all files in the charmap directory.  */
590   dir = charmap_opendir (CHARMAP_PATH);
591   if (dir == NULL)
592     return;
593
594   while ((dirent = charmap_readdir (dir)) != NULL)
595     {
596       char **aliases;
597       char **p;
598
599       PUT (xstrdup (dirent));
600
601       aliases = charmap_aliases (CHARMAP_PATH, dirent);
602
603 #if 0
604       /* Add the code_set_name and the aliases.  */
605       for (p = aliases; *p; p++)
606         PUT (xstrdup (*p));
607 #else
608       /* Add the code_set_name only.  Most aliases are obsolete.  */
609       p = aliases;
610       if (*p)
611         PUT (xstrdup (*p));
612 #endif
613
614       charmap_free_aliases (aliases);
615     }
616
617   charmap_closedir (dir);
618
619   twalk (all_data, print_names);
620 }
621
622
623 /* We have to show the contents of the environments determining the
624    locale.  */
625 static void
626 show_locale_vars (void)
627 {
628   size_t cat_no;
629   const char *lcall = getenv ("LC_ALL");
630   const char *lang = getenv ("LANG") ? : "POSIX";
631
632   auto void get_source (const char *name);
633
634   void get_source (const char *name)
635     {
636       char *val = getenv (name);
637
638       if ((lcall ?: "")[0] != '\0' || val == NULL)
639         printf ("%s=\"%s\"\n", name, (lcall ?: "")[0] ? lcall : lang);
640       else
641         printf ("%s=%s\n", name, val);
642     }
643
644   /* LANG has to be the first value.  */
645   printf ("LANG=%s\n", lang);
646
647   /* Now all categories in an unspecified order.  */
648   for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
649     if (cat_no != LC_ALL)
650       get_source (category[cat_no].name);
651
652   /* The last is the LC_ALL value.  */
653   printf ("LC_ALL=%s\n", lcall ? : "");
654 }
655
656
657 /* Show the information request for NAME.  */
658 static void
659 show_info (const char *name)
660 {
661   size_t cat_no;
662
663   auto void print_item (struct cat_item *item);
664
665   void print_item (struct cat_item *item)
666     {
667       switch (item->value_type)
668         {
669         case string:
670           if (show_keyword_name)
671             printf ("%s=\"", item->name);
672           fputs (nl_langinfo (item->item_id) ? : "", stdout);
673           if (show_keyword_name)
674             putchar ('"');
675           putchar ('\n');
676           break;
677         case stringarray:
678           {
679             int cnt;
680             const char *val;
681
682             if (show_keyword_name)
683               printf ("%s=\"", item->name);
684
685             for (cnt = 0; cnt < item->max - 1; ++cnt)
686               {
687                 val = nl_langinfo (item->item_id + cnt);
688                 if (val != NULL)
689                   fputs (val, stdout);
690                 putchar (';');
691               }
692
693             val = nl_langinfo (item->item_id + cnt);
694             if (val != NULL)
695               fputs (val, stdout);
696
697             if (show_keyword_name)
698               putchar ('"');
699             putchar ('\n');
700           }
701           break;
702         case stringlist:
703           {
704             int first = 1;
705             const char *val = nl_langinfo (item->item_id) ? : "";
706             int cnt;
707
708             if (show_keyword_name)
709               printf ("%s=", item->name);
710
711             for (cnt = 0; cnt < item->max && *val != '\0'; ++cnt)
712               {
713                 printf ("%s%s%s%s", first ? "" : ";",
714                         show_keyword_name ? "\"" : "", val,
715                         show_keyword_name ? "\"" : "");
716                 val = strchr (val, '\0') + 1;
717                 first = 0;
718               }
719             putchar ('\n');
720           }
721           break;
722         case byte:
723           {
724             const char *val = nl_langinfo (item->item_id);
725
726             if (show_keyword_name)
727               printf ("%s=", item->name);
728
729             if (val != NULL)
730               printf ("%d", *val == CHAR_MAX ? -1 : *val);
731             putchar ('\n');
732           }
733           break;
734         case bytearray:
735           {
736             const char *val = nl_langinfo (item->item_id);
737             int cnt = val ? strlen (val) : 0;
738
739             if (show_keyword_name)
740               printf ("%s=", item->name);
741
742             while (cnt > 1)
743               {
744                 printf ("%d;", *val == CHAR_MAX ? -1 : *val);
745                 --cnt;
746                 ++val;
747               }
748
749             printf ("%d\n", cnt == 0 || *val == CHAR_MAX ? -1 : *val);
750           }
751           break;
752         case word:
753           {
754             unsigned int val =
755               (unsigned int) (unsigned long int) nl_langinfo (item->item_id);
756             if (show_keyword_name)
757               printf ("%s=", item->name);
758
759             printf ("%d\n", val);
760           }
761           break;
762         case wstring:
763         case wstringarray:
764         case wstringlist:
765           /* We don't print wide character information since the same
766              information is available in a multibyte string.  */
767         default:
768           break;
769
770         }
771     }
772
773   for (cat_no = 0; cat_no < NCATEGORIES; ++cat_no)
774     if (cat_no != LC_ALL)
775       {
776         size_t item_no;
777
778         if (strcmp (name, category[cat_no].name) == 0)
779           /* Print the whole category.  */
780           {
781             if (show_category_name != 0)
782               puts (category[cat_no].name);
783
784             for (item_no = 0; item_no < category[cat_no].number; ++item_no)
785               print_item (&category[cat_no].item_desc[item_no]);
786
787             return;
788           }
789
790         for (item_no = 0; item_no < category[cat_no].number; ++item_no)
791           if (strcmp (name, category[cat_no].item_desc[item_no].name) == 0)
792             {
793               if (show_category_name != 0)
794                 puts (category[cat_no].name);
795
796               print_item (&category[cat_no].item_desc[item_no]);
797               return;
798             }
799       }
800
801   /* The name is not a standard one.
802      For testing and perhaps advanced use allow some more symbols.  */
803   locale_special (name, show_category_name, show_keyword_name);
804 }