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