1 /* $NetBSD: strfmon.c,v 1.4 2006/03/19 01:50:49 christos Exp $ */ 2 3 /*- 4 * Copyright (c) 2001 Alexey Zelkin <phantom@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 */ 29 30 #include <sys/cdefs.h> 31 #if defined(LIBC_SCCS) && !defined(lint) 32 #if 0 33 __FBSDID("$FreeBSD: src/lib/libc/stdlib/strfmon.c,v 1.14 2003/03/20 08:18:55 ache Exp $"); 34 #else 35 __RCSID("$NetBSD: strfmon.c,v 1.4 2006/03/19 01:50:49 christos Exp $"); 36 #endif 37 #endif /* LIBC_SCCS and not lint */ 38 39 #if defined(__NetBSD__) 40 #include "namespace.h" 41 #include <monetary.h> 42 #endif 43 44 #include <sys/types.h> 45 #include <ctype.h> 46 #include <errno.h> 47 #include <limits.h> 48 #include <locale.h> 49 #include <stdarg.h> 50 #include <stdio.h> 51 #include <stdlib.h> 52 #include <string.h> 53 54 /* internal flags */ 55 #define NEED_GROUPING 0x01 /* print digits grouped (default) */ 56 #define SIGN_POSN_USED 0x02 /* '+' or '(' usage flag */ 57 #define LOCALE_POSN 0x04 /* use locale defined +/- (default) */ 58 #define PARENTH_POSN 0x08 /* enclose negative amount in () */ 59 #define SUPRESS_CURR_SYMBOL 0x10 /* supress the currency from output */ 60 #define LEFT_JUSTIFY 0x20 /* left justify */ 61 #define USE_INTL_CURRENCY 0x40 /* use international currency symbol */ 62 #define IS_NEGATIVE 0x80 /* is argument value negative ? */ 63 64 /* internal macros */ 65 #define PRINT(CH) do { \ 66 if (dst >= s + maxsize) \ 67 goto e2big_error; \ 68 *dst++ = CH; \ 69 } while (/* CONSTCOND */ 0) 70 71 #define PRINTS(STR) do { \ 72 const char *tmps = STR; \ 73 while (*tmps != '\0') \ 74 PRINT(*tmps++); \ 75 } while (/* CONSTCOND */ 0) 76 77 #define GET_NUMBER(VAR) do { \ 78 VAR = 0; \ 79 while (isdigit((unsigned char)*fmt)) { \ 80 VAR *= 10; \ 81 VAR += *fmt - '0'; \ 82 fmt++; \ 83 } \ 84 } while (/* CONSTCOND */ 0) 85 86 #define GRPCPY(howmany) do { \ 87 int i = howmany; \ 88 while (i-- > 0) { \ 89 avalue_size--; \ 90 *--bufend = *(avalue+avalue_size+padded); \ 91 } \ 92 } while (/* CONSTCOND */ 0) 93 94 #define GRPSEP do { \ 95 *--bufend = thousands_sep; \ 96 groups++; \ 97 } while (/* CONSTCOND */ 0) 98 99 static void __setup_vars(int, char *, char *, char *, const char **); 100 static int __calc_left_pad(int, char *); 101 static char *__format_grouped_double(double, int *, int, int, int); 102 103 ssize_t 104 strfmon(char * __restrict s, size_t maxsize, const char * __restrict format, 105 ...) 106 { 107 va_list ap; 108 char *dst; /* output destination pointer */ 109 const char *fmt; /* current format poistion pointer */ 110 struct lconv *lc; /* pointer to lconv structure */ 111 char *asciivalue; /* formatted double pointer */ 112 113 int flags; /* formatting options */ 114 int pad_char; /* padding character */ 115 int pad_size; /* pad size */ 116 int width; /* field width */ 117 int left_prec; /* left precision */ 118 int right_prec; /* right precision */ 119 double value; /* just value */ 120 char space_char = ' '; /* space after currency */ 121 122 char cs_precedes, /* values gathered from struct lconv */ 123 sep_by_space, 124 sign_posn, 125 *currency_symbol; 126 const char *signstr; 127 128 char *tmpptr; /* temporary vars */ 129 int sverrno; 130 131 va_start(ap, format); 132 133 lc = localeconv(); 134 dst = s; 135 fmt = format; 136 asciivalue = NULL; 137 currency_symbol = NULL; 138 pad_size = 0; 139 140 while (*fmt) { 141 /* pass nonformating characters AS IS */ 142 if (*fmt != '%') 143 goto literal; 144 145 /* '%' found ! */ 146 147 /* "%%" mean just '%' */ 148 if (*(fmt+1) == '%') { 149 fmt++; 150 literal: 151 PRINT(*fmt++); 152 continue; 153 } 154 155 /* set up initial values */ 156 flags = (NEED_GROUPING|LOCALE_POSN); 157 pad_char = ' '; /* padding character is "space" */ 158 left_prec = -1; /* no left precision specified */ 159 right_prec = -1; /* no right precision specified */ 160 width = -1; /* no width specified */ 161 value = 0; /* we have no value to print now */ 162 163 /* Flags */ 164 while (/* CONSTCOND */ 1) { 165 switch (*++fmt) { 166 case '=': /* fill character */ 167 pad_char = *++fmt; 168 if (pad_char == '\0') 169 goto format_error; 170 continue; 171 case '^': /* not group currency */ 172 flags &= ~(NEED_GROUPING); 173 continue; 174 case '+': /* use locale defined signs */ 175 if (flags & SIGN_POSN_USED) 176 goto format_error; 177 flags |= (SIGN_POSN_USED|LOCALE_POSN); 178 continue; 179 case '(': /* enclose negatives with () */ 180 if (flags & SIGN_POSN_USED) 181 goto format_error; 182 flags |= (SIGN_POSN_USED|PARENTH_POSN); 183 continue; 184 case '!': /* suppress currency symbol */ 185 flags |= SUPRESS_CURR_SYMBOL; 186 continue; 187 case '-': /* alignment (left) */ 188 flags |= LEFT_JUSTIFY; 189 continue; 190 default: 191 break; 192 } 193 break; 194 } 195 196 /* field Width */ 197 if (isdigit((unsigned char)*fmt)) { 198 GET_NUMBER(width); 199 /* Do we have enough space to put number with 200 * required width ? 201 */ 202 if (dst + width >= s + maxsize) 203 goto e2big_error; 204 } 205 206 /* Left precision */ 207 if (*fmt == '#') { 208 if (!isdigit((unsigned char)*++fmt)) 209 goto format_error; 210 GET_NUMBER(left_prec); 211 } 212 213 /* Right precision */ 214 if (*fmt == '.') { 215 if (!isdigit((unsigned char)*++fmt)) 216 goto format_error; 217 GET_NUMBER(right_prec); 218 } 219 220 /* Conversion Characters */ 221 switch (*fmt++) { 222 case 'i': /* use internaltion currency format */ 223 flags |= USE_INTL_CURRENCY; 224 break; 225 case 'n': /* use national currency format */ 226 flags &= ~(USE_INTL_CURRENCY); 227 break; 228 default: /* required character is missing or 229 premature EOS */ 230 goto format_error; 231 } 232 233 if (currency_symbol) 234 free(currency_symbol); 235 if (flags & USE_INTL_CURRENCY) { 236 currency_symbol = strdup(lc->int_curr_symbol); 237 if (currency_symbol != NULL) 238 space_char = *(currency_symbol+3); 239 } else 240 currency_symbol = strdup(lc->currency_symbol); 241 242 if (currency_symbol == NULL) 243 goto end_error; /* ENOMEM. */ 244 245 /* value itself */ 246 value = va_arg(ap, double); 247 248 /* detect sign */ 249 if (value < 0) { 250 flags |= IS_NEGATIVE; 251 value = -value; 252 } 253 254 /* fill left_prec with amount of padding chars */ 255 if (left_prec >= 0) { 256 pad_size = __calc_left_pad((flags ^ IS_NEGATIVE), 257 currency_symbol) - 258 __calc_left_pad(flags, currency_symbol); 259 if (pad_size < 0) 260 pad_size = 0; 261 } 262 263 asciivalue = __format_grouped_double(value, &flags, 264 left_prec, right_prec, pad_char); 265 if (asciivalue == NULL) 266 goto end_error; /* errno already set */ 267 /* to ENOMEM by malloc() */ 268 269 /* set some variables for later use */ 270 __setup_vars(flags, &cs_precedes, &sep_by_space, 271 &sign_posn, &signstr); 272 273 /* 274 * Description of some LC_MONETARY's values: 275 * 276 * p_cs_precedes & n_cs_precedes 277 * 278 * = 1 - $currency_symbol precedes the value 279 * for a monetary quantity with a non-negative value 280 * = 0 - symbol succeeds the value 281 * 282 * p_sep_by_space & n_sep_by_space 283 * 284 * = 0 - no space separates $currency_symbol 285 * from the value for a monetary quantity with a 286 * non-negative value 287 * = 1 - space separates the symbol from the value 288 * = 2 - space separates the symbol and the sign string, 289 * if adjacent. 290 * 291 * p_sign_posn & n_sign_posn 292 * 293 * = 0 - parentheses enclose the quantity and the 294 * $currency_symbol 295 * = 1 - the sign string precedes the quantity and the 296 * $currency_symbol 297 * = 2 - the sign string succeeds the quantity and the 298 * $currency_symbol 299 * = 3 - the sign string precedes the $currency_symbol 300 * = 4 - the sign string succeeds the $currency_symbol 301 * 302 */ 303 304 tmpptr = dst; 305 306 while (pad_size-- > 0) 307 PRINT(' '); 308 309 if (sign_posn == 0 && (flags & IS_NEGATIVE)) 310 PRINT('('); 311 312 if (cs_precedes == 1) { 313 if (sign_posn == 1 || sign_posn == 3) { 314 PRINTS(signstr); 315 if (sep_by_space == 2) /* XXX: ? */ 316 PRINT(' '); 317 } 318 319 if (!(flags & SUPRESS_CURR_SYMBOL)) { 320 PRINTS(currency_symbol); 321 322 if (sign_posn == 4) { 323 if (sep_by_space == 2) 324 PRINT(space_char); 325 PRINTS(signstr); 326 if (sep_by_space == 1) 327 PRINT(' '); 328 } else if (sep_by_space == 1) 329 PRINT(space_char); 330 } 331 } else if (sign_posn == 1) 332 PRINTS(signstr); 333 334 PRINTS(asciivalue); 335 336 if (cs_precedes == 0) { 337 if (sign_posn == 3) { 338 if (sep_by_space == 1) 339 PRINT(' '); 340 PRINTS(signstr); 341 } 342 343 if (!(flags & SUPRESS_CURR_SYMBOL)) { 344 if ((sign_posn == 3 && sep_by_space == 2) 345 || (sep_by_space == 1 346 && (sign_posn == 0 347 || sign_posn == 1 348 || sign_posn == 2 349 || sign_posn == 4))) 350 PRINT(space_char); 351 PRINTS(currency_symbol); /* XXX: len */ 352 if (sign_posn == 4) { 353 if (sep_by_space == 2) 354 PRINT(' '); 355 PRINTS(signstr); 356 } 357 } 358 } 359 360 if (sign_posn == 2) { 361 if (sep_by_space == 2) 362 PRINT(' '); 363 PRINTS(signstr); 364 } 365 366 if (sign_posn == 0 && (flags & IS_NEGATIVE)) 367 PRINT(')'); 368 369 if (dst - tmpptr < width) { 370 if (flags & LEFT_JUSTIFY) { 371 while (dst - tmpptr < width) 372 PRINT(' '); 373 } else { 374 pad_size = dst-tmpptr; 375 memmove(tmpptr + width-pad_size, tmpptr, 376 (size_t) pad_size); 377 memset(tmpptr, ' ', (size_t) width-pad_size); 378 dst += width-pad_size; 379 } 380 } 381 } 382 383 PRINT('\0'); 384 va_end(ap); 385 free(asciivalue); 386 free(currency_symbol); 387 return (dst - s - 1); /* return size of put data except trailing '\0' */ 388 389 e2big_error: 390 errno = E2BIG; 391 goto end_error; 392 393 format_error: 394 errno = EINVAL; 395 396 end_error: 397 sverrno = errno; 398 if (asciivalue != NULL) 399 free(asciivalue); 400 if (currency_symbol != NULL) 401 free(currency_symbol); 402 errno = sverrno; 403 va_end(ap); 404 return (-1); 405 } 406 407 static void 408 __setup_vars(int flags, char *cs_precedes, char *sep_by_space, 409 char *sign_posn, const char **signstr) { 410 struct lconv *lc = localeconv(); 411 412 if ((flags & IS_NEGATIVE) && (flags & USE_INTL_CURRENCY)) { 413 *cs_precedes = lc->int_n_cs_precedes; 414 *sep_by_space = lc->int_n_sep_by_space; 415 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->int_n_sign_posn; 416 *signstr = (lc->negative_sign == '\0') ? "-" 417 : lc->negative_sign; 418 } else if (flags & USE_INTL_CURRENCY) { 419 *cs_precedes = lc->int_p_cs_precedes; 420 *sep_by_space = lc->int_p_sep_by_space; 421 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->int_p_sign_posn; 422 *signstr = lc->positive_sign; 423 } else if (flags & IS_NEGATIVE) { 424 *cs_precedes = lc->n_cs_precedes; 425 *sep_by_space = lc->n_sep_by_space; 426 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->n_sign_posn; 427 *signstr = (lc->negative_sign == '\0') ? "-" 428 : lc->negative_sign; 429 } else { 430 *cs_precedes = lc->p_cs_precedes; 431 *sep_by_space = lc->p_sep_by_space; 432 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->p_sign_posn; 433 *signstr = lc->positive_sign; 434 } 435 436 /* Set defult values for unspecified information. */ 437 if (*cs_precedes != 0) 438 *cs_precedes = 1; 439 if (*sep_by_space == CHAR_MAX) 440 *sep_by_space = 0; 441 if (*sign_posn == CHAR_MAX) 442 *sign_posn = 0; 443 } 444 445 static int 446 __calc_left_pad(int flags, char *cur_symb) { 447 448 char cs_precedes, sep_by_space, sign_posn; 449 const char *signstr; 450 int left_chars = 0; 451 452 __setup_vars(flags, &cs_precedes, &sep_by_space, &sign_posn, &signstr); 453 454 if (cs_precedes != 0) { 455 left_chars += strlen(cur_symb); 456 if (sep_by_space != 0) 457 left_chars++; 458 } 459 460 switch (sign_posn) { 461 case 1: 462 left_chars += strlen(signstr); 463 break; 464 case 3: 465 case 4: 466 if (cs_precedes != 0) 467 left_chars += strlen(signstr); 468 } 469 return (left_chars); 470 } 471 472 static int 473 get_groups(int size, char *grouping) { 474 475 int chars = 0; 476 477 if (*grouping == CHAR_MAX || *grouping <= 0) /* no grouping ? */ 478 return (0); 479 480 while (size > (int)*grouping) { 481 chars++; 482 size -= (int)*grouping++; 483 /* no more grouping ? */ 484 if (*grouping == CHAR_MAX) 485 break; 486 /* rest grouping with same value ? */ 487 if (*grouping == 0) { 488 chars += (size - 1) / *(grouping - 1); 489 break; 490 } 491 } 492 return (chars); 493 } 494 495 /* convert double to ASCII */ 496 static char * 497 __format_grouped_double(double value, int *flags, 498 int left_prec, int right_prec, int pad_char) { 499 500 char *rslt; 501 char *avalue; 502 int avalue_size; 503 char fmt[32]; 504 505 size_t bufsize; 506 char *bufend; 507 508 int padded; 509 510 struct lconv *lc = localeconv(); 511 char *grouping; 512 char decimal_point; 513 char thousands_sep; 514 515 int groups = 0; 516 517 grouping = lc->mon_grouping; 518 decimal_point = *lc->mon_decimal_point; 519 if (decimal_point == '\0') 520 decimal_point = *lc->decimal_point; 521 thousands_sep = *lc->mon_thousands_sep; 522 if (thousands_sep == '\0') 523 thousands_sep = *lc->thousands_sep; 524 525 /* fill left_prec with default value */ 526 if (left_prec == -1) 527 left_prec = 0; 528 529 /* fill right_prec with default value */ 530 if (right_prec == -1) { 531 if (*flags & USE_INTL_CURRENCY) 532 right_prec = lc->int_frac_digits; 533 else 534 right_prec = lc->frac_digits; 535 536 if (right_prec == CHAR_MAX) /* POSIX locale ? */ 537 right_prec = 2; 538 } 539 540 if (*flags & NEED_GROUPING) 541 left_prec += get_groups(left_prec, grouping); 542 543 /* convert to string */ 544 snprintf(fmt, sizeof(fmt), "%%%d.%df", left_prec + right_prec + 1, 545 right_prec); 546 avalue_size = asprintf(&avalue, fmt, value); 547 if (avalue_size < 0) 548 return (NULL); 549 550 /* make sure that we've enough space for result string */ 551 bufsize = strlen(avalue)*2+1; 552 rslt = malloc(bufsize); 553 if (rslt == NULL) { 554 free(avalue); 555 return (NULL); 556 } 557 memset(rslt, 0, bufsize); 558 bufend = rslt + bufsize - 1; /* reserve space for trailing '\0' */ 559 560 /* skip spaces at beggining */ 561 padded = 0; 562 while (avalue[padded] == ' ') { 563 padded++; 564 avalue_size--; 565 } 566 567 if (right_prec > 0) { 568 bufend -= right_prec; 569 memcpy(bufend, avalue + avalue_size+padded-right_prec, 570 (size_t) right_prec); 571 *--bufend = decimal_point; 572 avalue_size -= (right_prec + 1); 573 } 574 575 if ((*flags & NEED_GROUPING) && 576 thousands_sep != '\0' && /* XXX: need investigation */ 577 *grouping != CHAR_MAX && 578 *grouping > 0) { 579 while (avalue_size > (int)*grouping) { 580 GRPCPY(*grouping); 581 GRPSEP; 582 grouping++; 583 584 /* no more grouping ? */ 585 if (*grouping == CHAR_MAX) 586 break; 587 588 /* rest grouping with same value ? */ 589 if (*grouping == 0) { 590 grouping--; 591 while (avalue_size > *grouping) { 592 GRPCPY(*grouping); 593 GRPSEP; 594 } 595 } 596 } 597 if (avalue_size != 0) 598 GRPCPY(avalue_size); 599 padded -= groups; 600 601 } else { 602 bufend -= avalue_size; 603 memcpy(bufend, avalue+padded, (size_t) avalue_size); 604 if (right_prec == 0) 605 padded--; /* decrease assumed $decimal_point */ 606 } 607 608 /* do padding with pad_char */ 609 if (padded > 0) { 610 bufend -= padded; 611 memset(bufend, pad_char, (size_t) padded); 612 } 613 614 bufsize = bufsize - (bufend - rslt) + 1; 615 memmove(rslt, bufend, bufsize); 616 free(avalue); 617 return (rslt); 618 } 619