Home | History | Annotate | Line # | Download | only in locale
locale.c revision 1.3
      1 /*	$NetBSD: locale.c,v 1.3 2004/01/05 23:23:35 jmmv Exp $	*/
      2 
      3 /*-
      4  * Copyright (c) 2002, 2003 Alexey Zelkin <phantom (at) FreeBSD.org>
      5  * All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  * 1. Redistributions of source code must retain the above copyright
     11  *    notice, this list of conditions and the following disclaimer.
     12  * 2. Redistributions in binary form must reproduce the above copyright
     13  *    notice, this list of conditions and the following disclaimer in the
     14  *    documentation and/or other materials provided with the distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
     17  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
     20  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     24  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     25  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     26  * SUCH DAMAGE.
     27  *
     28  * FreeBSD: src/usr.bin/locale/locale.c,v 1.10 2003/06/26 11:05:56 phantom Exp
     29  */
     30 
     31 #include <sys/cdefs.h>
     32 #if defined(LIBC_SCCS) && !defined(lint)
     33 __RCSID("$NetBSD: locale.c,v 1.3 2004/01/05 23:23:35 jmmv Exp $");
     34 #endif /* LIBC_SCCS and not lint */
     35 
     36 /*
     37  * XXX: implement missing era_* (LC_TIME) keywords (require libc &
     38  *	nl_langinfo(3) extensions)
     39  *
     40  * XXX: correctly handle reserved 'charmap' keyword and '-m' option (require
     41  *      localedef(1) implementation).  Currently it's handled via
     42  *	nl_langinfo(CODESET).
     43  */
     44 
     45 #include <sys/types.h>
     46 #include <dirent.h>
     47 #include <err.h>
     48 #include <locale.h>
     49 #include <langinfo.h>
     50 #include <paths.h>
     51 #include <stdio.h>
     52 #include <stdlib.h>
     53 #include <string.h>
     54 #include <stringlist.h>
     55 #include <unistd.h>
     56 
     57 /* Local prototypes */
     58 void	init_locales_list(void);
     59 void	list_charmaps(void);
     60 void	list_locales(void);
     61 const char *lookup_localecat(int);
     62 char	*kwval_lconv(int);
     63 int	kwval_lookup(char *, char **, int *, int *);
     64 void	showdetails(char *);
     65 void	showkeywordslist(void);
     66 void	showlocale(void);
     67 void	usage(void);
     68 
     69 /* Global variables */
     70 static StringList *locales = NULL;
     71 extern char *_PathLocale;
     72 
     73 int	all_locales = 0;
     74 int	all_charmaps = 0;
     75 int	prt_categories = 0;
     76 int	prt_keywords = 0;
     77 int	more_params = 0;
     78 
     79 struct _lcinfo {
     80 	const char	*name;
     81 	int		id;
     82 } lcinfo [] = {
     83 	{ "LC_CTYPE",		LC_CTYPE },
     84 	{ "LC_COLLATE",		LC_COLLATE },
     85 	{ "LC_TIME",		LC_TIME },
     86 	{ "LC_NUMERIC",		LC_NUMERIC },
     87 	{ "LC_MONETARY",	LC_MONETARY },
     88 	{ "LC_MESSAGES",	LC_MESSAGES }
     89 };
     90 #define NLCINFO (sizeof(lcinfo)/sizeof(lcinfo[0]))
     91 
     92 /* ids for values not referenced by nl_langinfo() */
     93 #define	KW_ZERO			10000
     94 #define	KW_GROUPING		(KW_ZERO+1)
     95 #define KW_INT_CURR_SYMBOL 	(KW_ZERO+2)
     96 #define KW_CURRENCY_SYMBOL 	(KW_ZERO+3)
     97 #define KW_MON_DECIMAL_POINT 	(KW_ZERO+4)
     98 #define KW_MON_THOUSANDS_SEP 	(KW_ZERO+5)
     99 #define KW_MON_GROUPING 	(KW_ZERO+6)
    100 #define KW_POSITIVE_SIGN 	(KW_ZERO+7)
    101 #define KW_NEGATIVE_SIGN 	(KW_ZERO+8)
    102 #define KW_INT_FRAC_DIGITS 	(KW_ZERO+9)
    103 #define KW_FRAC_DIGITS 		(KW_ZERO+10)
    104 #define KW_P_CS_PRECEDES 	(KW_ZERO+11)
    105 #define KW_P_SEP_BY_SPACE 	(KW_ZERO+12)
    106 #define KW_N_CS_PRECEDES 	(KW_ZERO+13)
    107 #define KW_N_SEP_BY_SPACE 	(KW_ZERO+14)
    108 #define KW_P_SIGN_POSN 		(KW_ZERO+15)
    109 #define KW_N_SIGN_POSN 		(KW_ZERO+16)
    110 #define KW_INT_P_CS_PRECEDES 	(KW_ZERO+17)
    111 #define KW_INT_P_SEP_BY_SPACE 	(KW_ZERO+18)
    112 #define KW_INT_N_CS_PRECEDES 	(KW_ZERO+19)
    113 #define KW_INT_N_SEP_BY_SPACE 	(KW_ZERO+20)
    114 #define KW_INT_P_SIGN_POSN 	(KW_ZERO+21)
    115 #define KW_INT_N_SIGN_POSN 	(KW_ZERO+22)
    116 
    117 struct _kwinfo {
    118 	const char	*name;
    119 	int		isstr;		/* true - string, false - number */
    120 	int		catid;		/* LC_* */
    121 	int		value_ref;
    122 	const char	*comment;
    123 } kwinfo [] = {
    124 	{ "charmap",		1, LC_CTYPE,	CODESET, "" },	/* hack */
    125 
    126 	{ "decimal_point",	1, LC_NUMERIC,	RADIXCHAR, "" },
    127 	{ "thousands_sep",	1, LC_NUMERIC,	THOUSEP, "" },
    128 	{ "grouping",		1, LC_NUMERIC,	KW_GROUPING, "" },
    129 	{ "radixchar",		1, LC_NUMERIC,	RADIXCHAR,
    130 	  "Same as decimal_point (BSD only)" },			/* compat */
    131 	{ "thousep",		1, LC_NUMERIC,	THOUSEP,
    132 	  "Same as thousands_sep (BSD only)" },			/* compat */
    133 
    134 	{ "int_curr_symbol",	1, LC_MONETARY,	KW_INT_CURR_SYMBOL, "" },
    135 	{ "currency_symbol",	1, LC_MONETARY,	KW_CURRENCY_SYMBOL, "" },
    136 	{ "mon_decimal_point",	1, LC_MONETARY,	KW_MON_DECIMAL_POINT, "" },
    137 	{ "mon_thousands_sep",	1, LC_MONETARY,	KW_MON_THOUSANDS_SEP, "" },
    138 	{ "mon_grouping",	1, LC_MONETARY,	KW_MON_GROUPING, "" },
    139 	{ "positive_sign",	1, LC_MONETARY,	KW_POSITIVE_SIGN, "" },
    140 	{ "negative_sign",	1, LC_MONETARY,	KW_NEGATIVE_SIGN, "" },
    141 
    142 	{ "int_frac_digits",	0, LC_MONETARY,	KW_INT_FRAC_DIGITS, "" },
    143 	{ "frac_digits",	0, LC_MONETARY,	KW_FRAC_DIGITS, "" },
    144 	{ "p_cs_precedes",	0, LC_MONETARY,	KW_P_CS_PRECEDES, "" },
    145 	{ "p_sep_by_space",	0, LC_MONETARY,	KW_P_SEP_BY_SPACE, "" },
    146 	{ "n_cs_precedes",	0, LC_MONETARY,	KW_N_CS_PRECEDES, "" },
    147 	{ "n_sep_by_space",	0, LC_MONETARY,	KW_N_SEP_BY_SPACE, "" },
    148 	{ "p_sign_posn",	0, LC_MONETARY,	KW_P_SIGN_POSN, "" },
    149 	{ "n_sign_posn",	0, LC_MONETARY,	KW_N_SIGN_POSN, "" },
    150 	{ "int_p_cs_precedes",	0, LC_MONETARY,	KW_INT_P_CS_PRECEDES, "" },
    151 	{ "int_p_sep_by_space",	0, LC_MONETARY,	KW_INT_P_SEP_BY_SPACE, "" },
    152 	{ "int_n_cs_precedes",	0, LC_MONETARY,	KW_INT_N_CS_PRECEDES, "" },
    153 	{ "int_n_sep_by_space",	0, LC_MONETARY,	KW_INT_N_SEP_BY_SPACE, "" },
    154 	{ "int_p_sign_posn",	0, LC_MONETARY,	KW_INT_P_SIGN_POSN, "" },
    155 	{ "int_n_sign_posn",	0, LC_MONETARY,	KW_INT_N_SIGN_POSN, "" },
    156 
    157 	{ "d_t_fmt",		1, LC_TIME,	D_T_FMT, "" },
    158 	{ "d_fmt",		1, LC_TIME,	D_FMT, "" },
    159 	{ "t_fmt",		1, LC_TIME,	T_FMT, "" },
    160 	{ "am_str",		1, LC_TIME,	AM_STR, "" },
    161 	{ "pm_str",		1, LC_TIME,	PM_STR, "" },
    162 	{ "t_fmt_ampm",		1, LC_TIME,	T_FMT_AMPM, "" },
    163 	{ "day_1",		1, LC_TIME,	DAY_1, "" },
    164 	{ "day_2",		1, LC_TIME,	DAY_2, "" },
    165 	{ "day_3",		1, LC_TIME,	DAY_3, "" },
    166 	{ "day_4",		1, LC_TIME,	DAY_4, "" },
    167 	{ "day_5",		1, LC_TIME,	DAY_5, "" },
    168 	{ "day_6",		1, LC_TIME,	DAY_6, "" },
    169 	{ "day_7",		1, LC_TIME,	DAY_7, "" },
    170 	{ "abday_1",		1, LC_TIME,	ABDAY_1, "" },
    171 	{ "abday_2",		1, LC_TIME,	ABDAY_2, "" },
    172 	{ "abday_3",		1, LC_TIME,	ABDAY_3, "" },
    173 	{ "abday_4",		1, LC_TIME,	ABDAY_4, "" },
    174 	{ "abday_5",		1, LC_TIME,	ABDAY_5, "" },
    175 	{ "abday_6",		1, LC_TIME,	ABDAY_6, "" },
    176 	{ "abday_7",		1, LC_TIME,	ABDAY_7, "" },
    177 	{ "mon_1",		1, LC_TIME,	MON_1, "" },
    178 	{ "mon_2",		1, LC_TIME,	MON_2, "" },
    179 	{ "mon_3",		1, LC_TIME,	MON_3, "" },
    180 	{ "mon_4",		1, LC_TIME,	MON_4, "" },
    181 	{ "mon_5",		1, LC_TIME,	MON_5, "" },
    182 	{ "mon_6",		1, LC_TIME,	MON_6, "" },
    183 	{ "mon_7",		1, LC_TIME,	MON_7, "" },
    184 	{ "mon_8",		1, LC_TIME,	MON_8, "" },
    185 	{ "mon_9",		1, LC_TIME,	MON_9, "" },
    186 	{ "mon_10",		1, LC_TIME,	MON_10, "" },
    187 	{ "mon_11",		1, LC_TIME,	MON_11, "" },
    188 	{ "mon_12",		1, LC_TIME,	MON_12, "" },
    189 	{ "abmon_1",		1, LC_TIME,	ABMON_1, "" },
    190 	{ "abmon_2",		1, LC_TIME,	ABMON_2, "" },
    191 	{ "abmon_3",		1, LC_TIME,	ABMON_3, "" },
    192 	{ "abmon_4",		1, LC_TIME,	ABMON_4, "" },
    193 	{ "abmon_5",		1, LC_TIME,	ABMON_5, "" },
    194 	{ "abmon_6",		1, LC_TIME,	ABMON_6, "" },
    195 	{ "abmon_7",		1, LC_TIME,	ABMON_7, "" },
    196 	{ "abmon_8",		1, LC_TIME,	ABMON_8, "" },
    197 	{ "abmon_9",		1, LC_TIME,	ABMON_9, "" },
    198 	{ "abmon_10",		1, LC_TIME,	ABMON_10, "" },
    199 	{ "abmon_11",		1, LC_TIME,	ABMON_11, "" },
    200 	{ "abmon_12",		1, LC_TIME,	ABMON_12, "" },
    201 	{ "era",		1, LC_TIME,	ERA, "(unavailable)" },
    202 	{ "era_d_fmt",		1, LC_TIME,	ERA_D_FMT, "(unavailable)" },
    203 	{ "era_d_t_fmt",	1, LC_TIME,	ERA_D_T_FMT, "(unavailable)" },
    204 	{ "era_t_fmt",		1, LC_TIME,	ERA_T_FMT, "(unavailable)" },
    205 	{ "alt_digits",		1, LC_TIME,	ALT_DIGITS, "" },
    206 
    207 	{ "yesexpr",		1, LC_MESSAGES, YESEXPR, "" },
    208 	{ "noexpr",		1, LC_MESSAGES, NOEXPR, "" },
    209 	{ "yesstr",		1, LC_MESSAGES, YESSTR,
    210 	  "(POSIX legacy)" },					/* compat */
    211 	{ "nostr",		1, LC_MESSAGES, NOSTR,
    212 	  "(POSIX legacy)" }					/* compat */
    213 
    214 };
    215 #define NKWINFO (sizeof(kwinfo)/sizeof(kwinfo[0]))
    216 
    217 int
    218 main(int argc, char *argv[])
    219 {
    220 	int	ch;
    221 	int	tmp;
    222 
    223 	while ((ch = getopt(argc, argv, "ackm")) != -1) {
    224 		switch (ch) {
    225 		case 'a':
    226 			all_locales = 1;
    227 			break;
    228 		case 'c':
    229 			prt_categories = 1;
    230 			break;
    231 		case 'k':
    232 			prt_keywords = 1;
    233 			break;
    234 		case 'm':
    235 			all_charmaps = 1;
    236 			break;
    237 		default:
    238 			usage();
    239 		}
    240 	}
    241 	argc -= optind;
    242 	argv += optind;
    243 
    244 	/* validate arguments */
    245 	if (all_locales && all_charmaps)
    246 		usage();
    247 	if ((all_locales || all_charmaps) && argc > 0)
    248 		usage();
    249 	if ((all_locales || all_charmaps) && (prt_categories || prt_keywords))
    250 		usage();
    251 	if ((prt_categories || prt_keywords) && argc <= 0)
    252 		usage();
    253 
    254 	/* process '-a' */
    255 	if (all_locales) {
    256 		list_locales();
    257 		exit(0);
    258 	}
    259 
    260 	/* process '-m' */
    261 	if (all_charmaps) {
    262 		list_charmaps();
    263 		exit(0);
    264 	}
    265 
    266 	/* check for special case '-k list' */
    267 	tmp = 0;
    268 	if (prt_keywords && argc > 0)
    269 		while (tmp < argc)
    270 			if (strcasecmp(argv[tmp++], "list") == 0) {
    271 				showkeywordslist();
    272 				exit(0);
    273 			}
    274 
    275 	/* process '-c' and/or '-k' */
    276 	if (prt_categories || prt_keywords || argc > 0) {
    277 		setlocale(LC_ALL, "");
    278 		while (argc > 0) {
    279 			showdetails(*argv);
    280 			argv++;
    281 			argc--;
    282 		}
    283 		exit(0);
    284 	}
    285 
    286 	/* no arguments, show current locale state */
    287 	showlocale();
    288 
    289 	return (0);
    290 }
    291 
    292 void
    293 usage(void)
    294 {
    295 	printf("usage: locale [ -a | -m ]\n"
    296                "       locale [ -ck ] name ...\n");
    297 	exit(1);
    298 }
    299 
    300 /*
    301  * Output information about all available locales
    302  *
    303  * XXX actually output of this function does not guarantee that locale
    304  *     is really available to application, since it can be broken or
    305  *     inconsistent thus setlocale() will fail.  Maybe add '-V' function to
    306  *     also validate these locales?
    307  */
    308 void
    309 list_locales(void)
    310 {
    311 	size_t i;
    312 
    313 	init_locales_list();
    314 	for (i = 0; i < locales->sl_cur; i++) {
    315 		printf("%s\n", locales->sl_str[i]);
    316 	}
    317 }
    318 
    319 /*
    320  * qsort() helper function
    321  */
    322 static int
    323 scmp(const void *s1, const void *s2)
    324 {
    325 	return strcmp(*(const char **)s1, *(const char **)s2);
    326 }
    327 
    328 /*
    329  * Output information about all available charmaps
    330  *
    331  * XXX this function is doing a task in hackish way, i.e. by scaning
    332  *     list of locales, spliting their codeset part and building list of
    333  *     them.
    334  */
    335 void
    336 list_charmaps(void)
    337 {
    338 	size_t i;
    339 	char *s, *cs;
    340 	StringList *charmaps;
    341 
    342 	/* initialize StringList */
    343 	charmaps = sl_init();
    344 	if (charmaps == NULL)
    345 		err(1, "could not allocate memory");
    346 
    347 	/* fetch locales list */
    348 	init_locales_list();
    349 
    350 	/* split codesets and build their list */
    351 	for (i = 0; i < locales->sl_cur; i++) {
    352 		s = locales->sl_str[i];
    353 		if ((cs = strchr(s, '.')) != NULL) {
    354 			cs++;
    355 			if (sl_find(charmaps, cs) == NULL)
    356 				sl_add(charmaps, cs);
    357 		}
    358 	}
    359 
    360 	/* add US-ASCII, if not yet added */
    361 	if (sl_find(charmaps, "US-ASCII") == NULL)
    362 		sl_add(charmaps, "US-ASCII");
    363 
    364 	/* sort the list */
    365 	qsort(charmaps->sl_str, charmaps->sl_cur, sizeof(char *), scmp);
    366 
    367 	/* print results */
    368 	for (i = 0; i < charmaps->sl_cur; i++) {
    369 		printf("%s\n", charmaps->sl_str[i]);
    370 	}
    371 }
    372 
    373 /*
    374  * Retrieve sorted list of system locales (or user locales, if PATH_LOCALE
    375  * environment variable is set)
    376  */
    377 void
    378 init_locales_list(void)
    379 {
    380 	DIR *dirp;
    381 	struct dirent *dp;
    382 
    383 	/* why call this function twice ? */
    384 	if (locales != NULL)
    385 		return;
    386 
    387 	/* initialize StringList */
    388 	locales = sl_init();
    389 	if (locales == NULL)
    390 		err(1, "could not allocate memory");
    391 
    392 	/* get actual locales directory name */
    393 	setlocale(LC_CTYPE, "C");
    394 	if (_PathLocale == NULL)
    395 		errx(1, "unable to find locales storage");
    396 
    397 	/* open locales directory */
    398 	dirp = opendir(_PathLocale);
    399 	if (dirp == NULL)
    400 		err(1, "could not open directory '%s'", _PathLocale);
    401 
    402 	/* scan directory and store its contents except "." and ".." */
    403 	while ((dp = readdir(dirp)) != NULL) {
    404 		if (*(dp->d_name) == '.')
    405 			continue;		/* exclude "." and ".." */
    406 		sl_add(locales, strdup(dp->d_name));
    407 	}
    408 	closedir(dirp);
    409 
    410         /* make sure that 'POSIX' and 'C' locales are present in the list.
    411 	 * POSIX 1003.1-2001 requires presence of 'POSIX' name only here, but
    412          * we also list 'C' for constistency
    413          */
    414 	if (sl_find(locales, "POSIX") == NULL)
    415 		sl_add(locales, "POSIX");
    416 
    417 	if (sl_find(locales, "C") == NULL)
    418 		sl_add(locales, "C");
    419 
    420 	/* make output nicer, sort the list */
    421 	qsort(locales->sl_str, locales->sl_cur, sizeof(char *), scmp);
    422 }
    423 
    424 /*
    425  * Show current locale status, depending on environment variables
    426  */
    427 void
    428 showlocale(void)
    429 {
    430 	size_t	i;
    431 	const char *lang, *vval, *eval;
    432 
    433 	setlocale(LC_ALL, "");
    434 
    435 	lang = getenv("LANG");
    436 	if (lang == NULL) {
    437 		lang = "";
    438 	}
    439 	printf("LANG=%s\n", lang);
    440 	/* XXX: if LANG is null, then set it to "C" to get implied values? */
    441 
    442 	for (i = 0; i < NLCINFO; i++) {
    443 		vval = setlocale(lcinfo[i].id, NULL);
    444 		eval = getenv(lcinfo[i].name);
    445 		if (eval != NULL && !strcmp(eval, vval)
    446 				&& strcmp(lang, vval)) {
    447 			/*
    448 			 * Appropriate environment variable set, its value
    449 			 * is valid and not overriden by LC_ALL
    450 			 *
    451 			 * XXX: possible side effect: if both LANG and
    452 			 * overriden environment variable are set into same
    453 			 * value, then it'll be assumed as 'implied'
    454 			 */
    455 			printf("%s=%s\n", lcinfo[i].name, vval);
    456 		} else {
    457 			printf("%s=\"%s\"\n", lcinfo[i].name, vval);
    458 		}
    459 	}
    460 
    461 	vval = getenv("LC_ALL");
    462 	if (vval == NULL) {
    463 		vval = "";
    464 	}
    465 	printf("LC_ALL=%s\n", vval);
    466 }
    467 
    468 /*
    469  * keyword value lookup helper (via localeconv())
    470  */
    471 char *
    472 kwval_lconv(int id)
    473 {
    474 	struct lconv *lc;
    475 	char *rval;
    476 
    477 	rval = NULL;
    478 	lc = localeconv();
    479 	switch (id) {
    480 		case KW_GROUPING:
    481 			rval = lc->grouping;
    482 			break;
    483 		case KW_INT_CURR_SYMBOL:
    484 			rval = lc->int_curr_symbol;
    485 			break;
    486 		case KW_CURRENCY_SYMBOL:
    487 			rval = lc->currency_symbol;
    488 			break;
    489 		case KW_MON_DECIMAL_POINT:
    490 			rval = lc->mon_decimal_point;
    491 			break;
    492 		case KW_MON_THOUSANDS_SEP:
    493 			rval = lc->mon_thousands_sep;
    494 			break;
    495 		case KW_MON_GROUPING:
    496 			rval = lc->mon_grouping;
    497 			break;
    498 		case KW_POSITIVE_SIGN:
    499 			rval = lc->positive_sign;
    500 			break;
    501 		case KW_NEGATIVE_SIGN:
    502 			rval = lc->negative_sign;
    503 			break;
    504 		case KW_INT_FRAC_DIGITS:
    505 			rval = &(lc->int_frac_digits);
    506 			break;
    507 		case KW_FRAC_DIGITS:
    508 			rval = &(lc->frac_digits);
    509 			break;
    510 		case KW_P_CS_PRECEDES:
    511 			rval = &(lc->p_cs_precedes);
    512 			break;
    513 		case KW_P_SEP_BY_SPACE:
    514 			rval = &(lc->p_sep_by_space);
    515 			break;
    516 		case KW_N_CS_PRECEDES:
    517 			rval = &(lc->n_cs_precedes);
    518 			break;
    519 		case KW_N_SEP_BY_SPACE:
    520 			rval = &(lc->n_sep_by_space);
    521 			break;
    522 		case KW_P_SIGN_POSN:
    523 			rval = &(lc->p_sign_posn);
    524 			break;
    525 		case KW_N_SIGN_POSN:
    526 			rval = &(lc->n_sign_posn);
    527 			break;
    528 		case KW_INT_P_CS_PRECEDES:
    529 			rval = &(lc->int_p_cs_precedes);
    530 			break;
    531 		case KW_INT_P_SEP_BY_SPACE:
    532 			rval = &(lc->int_p_sep_by_space);
    533 			break;
    534 		case KW_INT_N_CS_PRECEDES:
    535 			rval = &(lc->int_n_cs_precedes);
    536 			break;
    537 		case KW_INT_N_SEP_BY_SPACE:
    538 			rval = &(lc->int_n_sep_by_space);
    539 			break;
    540 		case KW_INT_P_SIGN_POSN:
    541 			rval = &(lc->int_p_sign_posn);
    542 			break;
    543 		case KW_INT_N_SIGN_POSN:
    544 			rval = &(lc->int_n_sign_posn);
    545 			break;
    546 		default:
    547 			break;
    548 	}
    549 	return (rval);
    550 }
    551 
    552 /*
    553  * keyword value and properties lookup
    554  */
    555 int
    556 kwval_lookup(char *kwname, char **kwval, int *cat, int *isstr)
    557 {
    558 	int	rval;
    559 	size_t	i;
    560 
    561 	rval = 0;
    562 	for (i = 0; i < NKWINFO; i++) {
    563 		if (strcasecmp(kwname, kwinfo[i].name) == 0) {
    564 			rval = 1;
    565 			*cat = kwinfo[i].catid;
    566 			*isstr = kwinfo[i].isstr;
    567 			if (kwinfo[i].value_ref < KW_ZERO) {
    568 				*kwval = nl_langinfo(kwinfo[i].value_ref);
    569 			} else {
    570 				*kwval = kwval_lconv(kwinfo[i].value_ref);
    571 			}
    572 			break;
    573 		}
    574 	}
    575 
    576 	return (rval);
    577 }
    578 
    579 /*
    580  * Show details about requested keyword according to '-k' and/or '-c'
    581  * command line options specified.
    582  */
    583 void
    584 showdetails(char *kw)
    585 {
    586 	int	isstr, cat, tmpval;
    587 	char	*kwval;
    588 
    589 	if (kwval_lookup(kw, &kwval, &cat, &isstr) == 0) {
    590 		/*
    591 		 * invalid keyword specified.
    592 		 * XXX: any actions?
    593 		 */
    594 		return;
    595 	}
    596 
    597 	if (prt_categories) {
    598 		printf("%s\n", lookup_localecat(cat));
    599 	}
    600 
    601 	if (prt_keywords) {
    602 		if (isstr) {
    603 			printf("%s=\"%s\"\n", kw, kwval);
    604 		} else {
    605 			tmpval = (char) *kwval;
    606 			printf("%s=%d\n", kw, tmpval);
    607 		}
    608 	}
    609 
    610 	if (!prt_categories && !prt_keywords) {
    611 		if (isstr) {
    612 			printf("%s\n", kwval);
    613 		} else {
    614 			tmpval = (char) *kwval;
    615 			printf("%d\n", tmpval);
    616 		}
    617 	}
    618 }
    619 
    620 /*
    621  * Convert locale category id into string
    622  */
    623 const char *
    624 lookup_localecat(int cat)
    625 {
    626 	size_t	i;
    627 
    628 	for (i = 0; i < NLCINFO; i++)
    629 		if (lcinfo[i].id == cat) {
    630 			return (lcinfo[i].name);
    631 		}
    632 	return ("UNKNOWN");
    633 }
    634 
    635 /*
    636  * Show list of keywords
    637  */
    638 void
    639 showkeywordslist(void)
    640 {
    641 	size_t	i;
    642 
    643 #define FMT "%-20s %-12s %-7s %-20s\n"
    644 
    645 	printf("List of available keywords\n\n");
    646 	printf(FMT, "Keyword", "Category", "Type", "Comment");
    647 	printf("-------------------- ------------ ------- --------------------\n");
    648 	for (i = 0; i < NKWINFO; i++) {
    649 		printf(FMT,
    650 			kwinfo[i].name,
    651 			lookup_localecat(kwinfo[i].catid),
    652 			(kwinfo[i].isstr == 0) ? "number" : "string",
    653 			kwinfo[i].comment);
    654 	}
    655 }
    656