14297a3b0SGarrett D'Amore /*
2*2d08521bSGarrett D'Amore * Copyright 2013 Garrett D'Amore <garrett@damore.org>
36b5e5868SGarrett D'Amore * Copyright 2010 Nexenta Systems, Inc. All rights reserved.
44297a3b0SGarrett D'Amore * Copyright (c) 2001 Alexey Zelkin <phantom@FreeBSD.org>
54297a3b0SGarrett D'Amore * All rights reserved.
64297a3b0SGarrett D'Amore *
74297a3b0SGarrett D'Amore * Redistribution and use in source and binary forms, with or without
84297a3b0SGarrett D'Amore * modification, are permitted provided that the following conditions
94297a3b0SGarrett D'Amore * are met:
104297a3b0SGarrett D'Amore * 1. Redistributions of source code must retain the above copyright
114297a3b0SGarrett D'Amore * notice, this list of conditions and the following disclaimer.
124297a3b0SGarrett D'Amore * 2. Redistributions in binary form must reproduce the above copyright
134297a3b0SGarrett D'Amore * notice, this list of conditions and the following disclaimer in the
144297a3b0SGarrett D'Amore * documentation and/or other materials provided with the distribution.
154297a3b0SGarrett D'Amore *
164297a3b0SGarrett D'Amore * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
174297a3b0SGarrett D'Amore * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
184297a3b0SGarrett D'Amore * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
194297a3b0SGarrett D'Amore * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
204297a3b0SGarrett D'Amore * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
214297a3b0SGarrett D'Amore * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
224297a3b0SGarrett D'Amore * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
234297a3b0SGarrett D'Amore * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
244297a3b0SGarrett D'Amore * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
254297a3b0SGarrett D'Amore * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
264297a3b0SGarrett D'Amore * SUCH DAMAGE.
274297a3b0SGarrett D'Amore *
284297a3b0SGarrett D'Amore */
294297a3b0SGarrett D'Amore
304297a3b0SGarrett D'Amore #ifndef _LCONV_C99
314297a3b0SGarrett D'Amore #define _LCONV_C99
324297a3b0SGarrett D'Amore #endif
334297a3b0SGarrett D'Amore
344297a3b0SGarrett D'Amore #include "lint.h"
354297a3b0SGarrett D'Amore #include <sys/types.h>
364297a3b0SGarrett D'Amore #include <ctype.h>
374297a3b0SGarrett D'Amore #include <errno.h>
384297a3b0SGarrett D'Amore #include <limits.h>
394297a3b0SGarrett D'Amore #include <locale.h>
404297a3b0SGarrett D'Amore #include <monetary.h>
414297a3b0SGarrett D'Amore #include <stdarg.h>
424297a3b0SGarrett D'Amore #include <stdio.h>
434297a3b0SGarrett D'Amore #include <stdlib.h>
444297a3b0SGarrett D'Amore #include <string.h>
45*2d08521bSGarrett D'Amore #include "localeimpl.h"
46*2d08521bSGarrett D'Amore #include "lmonetary.h"
47*2d08521bSGarrett D'Amore #include "lnumeric.h"
484297a3b0SGarrett D'Amore
494297a3b0SGarrett D'Amore /* internal flags */
504297a3b0SGarrett D'Amore #define NEED_GROUPING 0x01 /* print digits grouped (default) */
514297a3b0SGarrett D'Amore #define SIGN_POSN_USED 0x02 /* '+' or '(' usage flag */
524297a3b0SGarrett D'Amore #define LOCALE_POSN 0x04 /* use locale defined +/- (default) */
534297a3b0SGarrett D'Amore #define PARENTH_POSN 0x08 /* enclose negative amount in () */
544297a3b0SGarrett D'Amore #define SUPRESS_CURR_SYMBOL 0x10 /* supress the currency from output */
554297a3b0SGarrett D'Amore #define LEFT_JUSTIFY 0x20 /* left justify */
564297a3b0SGarrett D'Amore #define USE_INTL_CURRENCY 0x40 /* use international currency symbol */
574297a3b0SGarrett D'Amore #define IS_NEGATIVE 0x80 /* is argument value negative ? */
584297a3b0SGarrett D'Amore
594297a3b0SGarrett D'Amore /* internal macros */
604297a3b0SGarrett D'Amore #define PRINT(CH) { \
614297a3b0SGarrett D'Amore if (dst >= s + maxsize) \
624297a3b0SGarrett D'Amore goto e2big_error; \
634297a3b0SGarrett D'Amore *dst++ = CH; \
644297a3b0SGarrett D'Amore }
654297a3b0SGarrett D'Amore
664297a3b0SGarrett D'Amore #define PRINTS(STR) { \
67*2d08521bSGarrett D'Amore const char *tmps = STR; \
684297a3b0SGarrett D'Amore while (*tmps != '\0') \
694297a3b0SGarrett D'Amore PRINT(*tmps++); \
704297a3b0SGarrett D'Amore }
714297a3b0SGarrett D'Amore
724297a3b0SGarrett D'Amore #define GET_NUMBER(VAR) { \
734297a3b0SGarrett D'Amore VAR = 0; \
744297a3b0SGarrett D'Amore while (isdigit((unsigned char)*fmt)) { \
754297a3b0SGarrett D'Amore if (VAR > INT_MAX / 10) \
764297a3b0SGarrett D'Amore goto e2big_error; \
774297a3b0SGarrett D'Amore VAR *= 10; \
784297a3b0SGarrett D'Amore VAR += *fmt - '0'; \
794297a3b0SGarrett D'Amore if (VAR < 0) \
804297a3b0SGarrett D'Amore goto e2big_error; \
814297a3b0SGarrett D'Amore fmt++; \
824297a3b0SGarrett D'Amore } \
834297a3b0SGarrett D'Amore }
844297a3b0SGarrett D'Amore
854297a3b0SGarrett D'Amore #define GRPCPY(howmany) { \
864297a3b0SGarrett D'Amore int i = howmany; \
874297a3b0SGarrett D'Amore while (i-- > 0) { \
884297a3b0SGarrett D'Amore avalue_size--; \
894297a3b0SGarrett D'Amore *--bufend = *(avalue+avalue_size+padded); \
904297a3b0SGarrett D'Amore } \
914297a3b0SGarrett D'Amore }
924297a3b0SGarrett D'Amore
934297a3b0SGarrett D'Amore #define GRPSEP { \
94*2d08521bSGarrett D'Amore bufend -= thousands_len; \
95*2d08521bSGarrett D'Amore (void) memcpy(bufend, thousands_sep, thousands_len); \
964297a3b0SGarrett D'Amore groups++; \
974297a3b0SGarrett D'Amore }
984297a3b0SGarrett D'Amore
99*2d08521bSGarrett D'Amore static void setup_vars(const struct lc_monetary *, int, char *, char *, char *,
100*2d08521bSGarrett D'Amore const char **);
101*2d08521bSGarrett D'Amore static int calc_left_pad(const struct lc_monetary *, int, const char *);
102*2d08521bSGarrett D'Amore static char *format_grouped_double(const struct lc_monetary *,
103*2d08521bSGarrett D'Amore const struct lc_numeric *, double, int *, int, int, int);
1044297a3b0SGarrett D'Amore
1054297a3b0SGarrett D'Amore ssize_t
strfmon_impl(char * _RESTRICT_KYWD s,size_t maxsize,locale_t loc,const char * _RESTRICT_KYWD format,va_list ap)106*2d08521bSGarrett D'Amore strfmon_impl(char *_RESTRICT_KYWD s, size_t maxsize, locale_t loc,
107*2d08521bSGarrett D'Amore const char *_RESTRICT_KYWD format, va_list ap)
1084297a3b0SGarrett D'Amore {
1094297a3b0SGarrett D'Amore char *dst; /* output destination pointer */
1104297a3b0SGarrett D'Amore const char *fmt; /* current format poistion pointer */
1114297a3b0SGarrett D'Amore char *asciivalue; /* formatted double pointer */
1124297a3b0SGarrett D'Amore
1134297a3b0SGarrett D'Amore int flags; /* formatting options */
1144297a3b0SGarrett D'Amore int pad_char; /* padding character */
1154297a3b0SGarrett D'Amore int pad_size; /* pad size */
1164297a3b0SGarrett D'Amore int width; /* field width */
1174297a3b0SGarrett D'Amore int left_prec; /* left precision */
1184297a3b0SGarrett D'Amore int right_prec; /* right precision */
1194297a3b0SGarrett D'Amore double value; /* just value */
1204297a3b0SGarrett D'Amore char space_char = ' '; /* space after currency */
1214297a3b0SGarrett D'Amore
122*2d08521bSGarrett D'Amore char cs_precedes; /* values from struct lc_monetary */
1234297a3b0SGarrett D'Amore char sep_by_space;
1244297a3b0SGarrett D'Amore char sign_posn;
125*2d08521bSGarrett D'Amore const char *signstr;
126*2d08521bSGarrett D'Amore const char *currency_symbol;
1274297a3b0SGarrett D'Amore
1284297a3b0SGarrett D'Amore char *tmpptr; /* temporary vars */
1294297a3b0SGarrett D'Amore int sverrno;
130*2d08521bSGarrett D'Amore const struct lc_monetary *lmon; /* monetary structure */
131*2d08521bSGarrett D'Amore const struct lc_numeric *lnum; /* numeric structure */
1324297a3b0SGarrett D'Amore
133*2d08521bSGarrett D'Amore lmon = loc->monetary;
134*2d08521bSGarrett D'Amore lnum = loc->numeric;
1354297a3b0SGarrett D'Amore
1364297a3b0SGarrett D'Amore dst = s;
1374297a3b0SGarrett D'Amore fmt = format;
1384297a3b0SGarrett D'Amore asciivalue = NULL;
1394297a3b0SGarrett D'Amore currency_symbol = NULL;
1404297a3b0SGarrett D'Amore pad_size = 0;
1414297a3b0SGarrett D'Amore
1424297a3b0SGarrett D'Amore while (*fmt) {
1434297a3b0SGarrett D'Amore /* pass nonformating characters AS IS */
1444297a3b0SGarrett D'Amore if (*fmt != '%')
1454297a3b0SGarrett D'Amore goto literal;
1464297a3b0SGarrett D'Amore
1474297a3b0SGarrett D'Amore /* '%' found ! */
1484297a3b0SGarrett D'Amore
1494297a3b0SGarrett D'Amore /* "%%" mean just '%' */
1504297a3b0SGarrett D'Amore if (*(fmt+1) == '%') {
1514297a3b0SGarrett D'Amore fmt++;
1524297a3b0SGarrett D'Amore literal:
1534297a3b0SGarrett D'Amore PRINT(*fmt++);
1544297a3b0SGarrett D'Amore continue;
1554297a3b0SGarrett D'Amore }
1564297a3b0SGarrett D'Amore
1574297a3b0SGarrett D'Amore /* set up initial values */
1584297a3b0SGarrett D'Amore flags = (NEED_GROUPING|LOCALE_POSN);
1594297a3b0SGarrett D'Amore pad_char = ' '; /* padding character is "space" */
1604297a3b0SGarrett D'Amore left_prec = -1; /* no left precision specified */
1614297a3b0SGarrett D'Amore right_prec = -1; /* no right precision specified */
1624297a3b0SGarrett D'Amore width = -1; /* no width specified */
1634297a3b0SGarrett D'Amore value = 0; /* we have no value to print now */
1644297a3b0SGarrett D'Amore
1654297a3b0SGarrett D'Amore /* Flags */
1664297a3b0SGarrett D'Amore for (;;) {
1674297a3b0SGarrett D'Amore switch (*++fmt) {
1684297a3b0SGarrett D'Amore case '=': /* fill character */
1694297a3b0SGarrett D'Amore pad_char = *++fmt;
1704297a3b0SGarrett D'Amore if (pad_char == '\0')
1714297a3b0SGarrett D'Amore goto format_error;
1724297a3b0SGarrett D'Amore continue;
1734297a3b0SGarrett D'Amore case '^': /* not group currency */
1744297a3b0SGarrett D'Amore flags &= ~(NEED_GROUPING);
1754297a3b0SGarrett D'Amore continue;
1764297a3b0SGarrett D'Amore case '+': /* use locale defined signs */
1774297a3b0SGarrett D'Amore if (flags & SIGN_POSN_USED)
1784297a3b0SGarrett D'Amore goto format_error;
1794297a3b0SGarrett D'Amore flags |= (SIGN_POSN_USED|LOCALE_POSN);
1804297a3b0SGarrett D'Amore continue;
1814297a3b0SGarrett D'Amore case '(': /* enclose negatives with () */
1824297a3b0SGarrett D'Amore if (flags & SIGN_POSN_USED)
1834297a3b0SGarrett D'Amore goto format_error;
1844297a3b0SGarrett D'Amore flags |= (SIGN_POSN_USED|PARENTH_POSN);
1854297a3b0SGarrett D'Amore continue;
1864297a3b0SGarrett D'Amore case '!': /* suppress currency symbol */
1874297a3b0SGarrett D'Amore flags |= SUPRESS_CURR_SYMBOL;
1884297a3b0SGarrett D'Amore continue;
1894297a3b0SGarrett D'Amore case '-': /* alignment (left) */
1904297a3b0SGarrett D'Amore flags |= LEFT_JUSTIFY;
1914297a3b0SGarrett D'Amore continue;
1924297a3b0SGarrett D'Amore default:
1934297a3b0SGarrett D'Amore break;
1944297a3b0SGarrett D'Amore }
1954297a3b0SGarrett D'Amore break;
1964297a3b0SGarrett D'Amore }
1974297a3b0SGarrett D'Amore
1984297a3b0SGarrett D'Amore /* field Width */
1994297a3b0SGarrett D'Amore if (isdigit((unsigned char)*fmt)) {
2004297a3b0SGarrett D'Amore GET_NUMBER(width);
2014297a3b0SGarrett D'Amore /*
2024297a3b0SGarrett D'Amore * Do we have enough space to put number with
2034297a3b0SGarrett D'Amore * required width ?
2044297a3b0SGarrett D'Amore */
2054297a3b0SGarrett D'Amore if ((unsigned int)width >= maxsize - (dst - s))
2064297a3b0SGarrett D'Amore goto e2big_error;
2074297a3b0SGarrett D'Amore }
2084297a3b0SGarrett D'Amore
2094297a3b0SGarrett D'Amore /* Left precision */
2104297a3b0SGarrett D'Amore if (*fmt == '#') {
2114297a3b0SGarrett D'Amore if (!isdigit((unsigned char)*++fmt))
2124297a3b0SGarrett D'Amore goto format_error;
2134297a3b0SGarrett D'Amore GET_NUMBER(left_prec);
2144297a3b0SGarrett D'Amore if ((unsigned int)left_prec >= maxsize - (dst - s))
2154297a3b0SGarrett D'Amore goto e2big_error;
2164297a3b0SGarrett D'Amore }
2174297a3b0SGarrett D'Amore
2184297a3b0SGarrett D'Amore /* Right precision */
2194297a3b0SGarrett D'Amore if (*fmt == '.') {
2204297a3b0SGarrett D'Amore if (!isdigit((unsigned char)*++fmt))
2214297a3b0SGarrett D'Amore goto format_error;
2224297a3b0SGarrett D'Amore GET_NUMBER(right_prec);
2234297a3b0SGarrett D'Amore if ((unsigned int)right_prec >= maxsize - (dst - s) -
2244297a3b0SGarrett D'Amore left_prec)
2254297a3b0SGarrett D'Amore goto e2big_error;
2264297a3b0SGarrett D'Amore }
2274297a3b0SGarrett D'Amore
2284297a3b0SGarrett D'Amore /* Conversion Characters */
2294297a3b0SGarrett D'Amore switch (*fmt++) {
2304297a3b0SGarrett D'Amore case 'i': /* use internaltion currency format */
2314297a3b0SGarrett D'Amore flags |= USE_INTL_CURRENCY;
2324297a3b0SGarrett D'Amore break;
2334297a3b0SGarrett D'Amore case 'n': /* use national currency format */
2344297a3b0SGarrett D'Amore flags &= ~(USE_INTL_CURRENCY);
2354297a3b0SGarrett D'Amore break;
2364297a3b0SGarrett D'Amore default:
2374297a3b0SGarrett D'Amore /* required char missing or premature EOS */
2384297a3b0SGarrett D'Amore goto format_error;
2394297a3b0SGarrett D'Amore }
2404297a3b0SGarrett D'Amore
2414297a3b0SGarrett D'Amore if (flags & USE_INTL_CURRENCY) {
242*2d08521bSGarrett D'Amore currency_symbol = lmon->int_curr_symbol;
243*2d08521bSGarrett D'Amore /* by definition three letters followed by a space */
2444297a3b0SGarrett D'Amore if (currency_symbol != NULL)
245*2d08521bSGarrett D'Amore space_char = currency_symbol[3];
2464297a3b0SGarrett D'Amore } else
247*2d08521bSGarrett D'Amore currency_symbol = lmon->currency_symbol;
2484297a3b0SGarrett D'Amore
2494297a3b0SGarrett D'Amore /* value itself */
2504297a3b0SGarrett D'Amore value = va_arg(ap, double);
2514297a3b0SGarrett D'Amore
2524297a3b0SGarrett D'Amore /* detect sign */
2534297a3b0SGarrett D'Amore if (value < 0) {
2544297a3b0SGarrett D'Amore flags |= IS_NEGATIVE;
2554297a3b0SGarrett D'Amore value = -value;
2564297a3b0SGarrett D'Amore }
2574297a3b0SGarrett D'Amore
2584297a3b0SGarrett D'Amore /* fill left_prec with amount of padding chars */
2594297a3b0SGarrett D'Amore if (left_prec >= 0) {
260*2d08521bSGarrett D'Amore pad_size = calc_left_pad(lmon, (flags ^ IS_NEGATIVE),
2614297a3b0SGarrett D'Amore currency_symbol) -
262*2d08521bSGarrett D'Amore calc_left_pad(lmon, flags, currency_symbol);
2634297a3b0SGarrett D'Amore if (pad_size < 0)
2644297a3b0SGarrett D'Amore pad_size = 0;
2654297a3b0SGarrett D'Amore }
2664297a3b0SGarrett D'Amore
2674297a3b0SGarrett D'Amore if (asciivalue != NULL)
2684297a3b0SGarrett D'Amore free(asciivalue);
269*2d08521bSGarrett D'Amore asciivalue = format_grouped_double(lmon, lnum, value, &flags,
2704297a3b0SGarrett D'Amore left_prec, right_prec, pad_char);
2714297a3b0SGarrett D'Amore if (asciivalue == NULL)
2724297a3b0SGarrett D'Amore goto end_error; /* errno already set */
2734297a3b0SGarrett D'Amore /* to ENOMEM by malloc() */
2744297a3b0SGarrett D'Amore
2754297a3b0SGarrett D'Amore /* set some variables for later use */
276*2d08521bSGarrett D'Amore setup_vars(lmon, flags, &cs_precedes, &sep_by_space,
277*2d08521bSGarrett D'Amore &sign_posn, &signstr);
2784297a3b0SGarrett D'Amore
2794297a3b0SGarrett D'Amore /*
2804297a3b0SGarrett D'Amore * Description of some LC_MONETARY's values:
2814297a3b0SGarrett D'Amore *
2824297a3b0SGarrett D'Amore * p_cs_precedes & n_cs_precedes
2834297a3b0SGarrett D'Amore *
2844297a3b0SGarrett D'Amore * = 1 - $currency_symbol precedes the value
2854297a3b0SGarrett D'Amore * for a monetary quantity with a non-negative value
2864297a3b0SGarrett D'Amore * = 0 - symbol succeeds the value
2874297a3b0SGarrett D'Amore *
2884297a3b0SGarrett D'Amore * p_sep_by_space & n_sep_by_space
2894297a3b0SGarrett D'Amore *
2904297a3b0SGarrett D'Amore * = 0 - no space separates $currency_symbol
2914297a3b0SGarrett D'Amore * from the value for a monetary quantity with a
2924297a3b0SGarrett D'Amore * non-negative value
2934297a3b0SGarrett D'Amore * = 1 - space separates the symbol from the value
2944297a3b0SGarrett D'Amore * = 2 - space separates the symbol and the sign string,
2954297a3b0SGarrett D'Amore * if adjacent.
2964297a3b0SGarrett D'Amore *
2974297a3b0SGarrett D'Amore * p_sign_posn & n_sign_posn
2984297a3b0SGarrett D'Amore *
2994297a3b0SGarrett D'Amore * = 0 - parentheses enclose the quantity and the
3004297a3b0SGarrett D'Amore * $currency_symbol
3014297a3b0SGarrett D'Amore * = 1 - the sign string precedes the quantity and the
3024297a3b0SGarrett D'Amore * $currency_symbol
3034297a3b0SGarrett D'Amore * = 2 - the sign string succeeds the quantity and the
3044297a3b0SGarrett D'Amore * $currency_symbol
3054297a3b0SGarrett D'Amore * = 3 - the sign string precedes the $currency_symbol
3064297a3b0SGarrett D'Amore * = 4 - the sign string succeeds the $currency_symbol
3074297a3b0SGarrett D'Amore *
3084297a3b0SGarrett D'Amore */
3094297a3b0SGarrett D'Amore
3104297a3b0SGarrett D'Amore tmpptr = dst;
3114297a3b0SGarrett D'Amore
3124297a3b0SGarrett D'Amore while (pad_size-- > 0)
3134297a3b0SGarrett D'Amore PRINT(' ');
3144297a3b0SGarrett D'Amore
3154297a3b0SGarrett D'Amore if (sign_posn == 0 && (flags & IS_NEGATIVE))
3164297a3b0SGarrett D'Amore PRINT('(');
3174297a3b0SGarrett D'Amore
3184297a3b0SGarrett D'Amore if (cs_precedes == 1) {
3194297a3b0SGarrett D'Amore if (sign_posn == 1 || sign_posn == 3) {
3204297a3b0SGarrett D'Amore PRINTS(signstr);
321*2d08521bSGarrett D'Amore if (sep_by_space == 2)
3224297a3b0SGarrett D'Amore PRINT(' ');
3234297a3b0SGarrett D'Amore }
3244297a3b0SGarrett D'Amore
3254297a3b0SGarrett D'Amore if (!(flags & SUPRESS_CURR_SYMBOL)) {
3264297a3b0SGarrett D'Amore PRINTS(currency_symbol);
3274297a3b0SGarrett D'Amore
3284297a3b0SGarrett D'Amore if (sign_posn == 4) {
3294297a3b0SGarrett D'Amore if (sep_by_space == 2)
3304297a3b0SGarrett D'Amore PRINT(space_char);
3314297a3b0SGarrett D'Amore PRINTS(signstr);
3324297a3b0SGarrett D'Amore if (sep_by_space == 1)
3334297a3b0SGarrett D'Amore PRINT(' ');
3344297a3b0SGarrett D'Amore } else if (sep_by_space == 1)
3354297a3b0SGarrett D'Amore PRINT(space_char);
3364297a3b0SGarrett D'Amore }
3374297a3b0SGarrett D'Amore } else if (sign_posn == 1)
3384297a3b0SGarrett D'Amore PRINTS(signstr);
3394297a3b0SGarrett D'Amore
3404297a3b0SGarrett D'Amore PRINTS(asciivalue);
3414297a3b0SGarrett D'Amore
3424297a3b0SGarrett D'Amore if (cs_precedes == 0) {
3434297a3b0SGarrett D'Amore if (sign_posn == 3) {
3444297a3b0SGarrett D'Amore if (sep_by_space == 1)
3454297a3b0SGarrett D'Amore PRINT(' ');
3464297a3b0SGarrett D'Amore PRINTS(signstr);
3474297a3b0SGarrett D'Amore }
3484297a3b0SGarrett D'Amore
3494297a3b0SGarrett D'Amore if (!(flags & SUPRESS_CURR_SYMBOL)) {
3504297a3b0SGarrett D'Amore if ((sign_posn == 3 && sep_by_space == 2) ||
3514297a3b0SGarrett D'Amore (sep_by_space == 1 && (sign_posn == 0 ||
3524297a3b0SGarrett D'Amore sign_posn == 1 || sign_posn == 2 ||
3534297a3b0SGarrett D'Amore sign_posn == 4)))
3544297a3b0SGarrett D'Amore PRINT(space_char);
3554297a3b0SGarrett D'Amore PRINTS(currency_symbol); /* XXX: len */
3564297a3b0SGarrett D'Amore if (sign_posn == 4) {
3574297a3b0SGarrett D'Amore if (sep_by_space == 2)
3584297a3b0SGarrett D'Amore PRINT(' ');
3594297a3b0SGarrett D'Amore PRINTS(signstr);
3604297a3b0SGarrett D'Amore }
3614297a3b0SGarrett D'Amore }
3624297a3b0SGarrett D'Amore }
3634297a3b0SGarrett D'Amore
3644297a3b0SGarrett D'Amore if (sign_posn == 2) {
3654297a3b0SGarrett D'Amore if (sep_by_space == 2)
3664297a3b0SGarrett D'Amore PRINT(' ');
3674297a3b0SGarrett D'Amore PRINTS(signstr);
3684297a3b0SGarrett D'Amore }
3694297a3b0SGarrett D'Amore
3704297a3b0SGarrett D'Amore if (sign_posn == 0 && (flags & IS_NEGATIVE))
3714297a3b0SGarrett D'Amore PRINT(')');
3724297a3b0SGarrett D'Amore
3734297a3b0SGarrett D'Amore if (dst - tmpptr < width) {
3744297a3b0SGarrett D'Amore if (flags & LEFT_JUSTIFY) {
3754297a3b0SGarrett D'Amore while (dst - tmpptr < width)
3764297a3b0SGarrett D'Amore PRINT(' ');
3774297a3b0SGarrett D'Amore } else {
3784297a3b0SGarrett D'Amore pad_size = dst-tmpptr;
379eda71b4aSGarrett D'Amore (void) memmove(tmpptr + width-pad_size, tmpptr,
3804297a3b0SGarrett D'Amore pad_size);
3814297a3b0SGarrett D'Amore (void) memset(tmpptr, ' ', width-pad_size);
3824297a3b0SGarrett D'Amore dst += width-pad_size;
3834297a3b0SGarrett D'Amore }
3844297a3b0SGarrett D'Amore }
3854297a3b0SGarrett D'Amore }
3864297a3b0SGarrett D'Amore
3874297a3b0SGarrett D'Amore PRINT('\0');
3884297a3b0SGarrett D'Amore free(asciivalue);
3894297a3b0SGarrett D'Amore return (dst - s - 1); /* size of put data except trailing '\0' */
3904297a3b0SGarrett D'Amore
3914297a3b0SGarrett D'Amore e2big_error:
3924297a3b0SGarrett D'Amore errno = E2BIG;
3934297a3b0SGarrett D'Amore goto end_error;
3944297a3b0SGarrett D'Amore
3954297a3b0SGarrett D'Amore format_error:
3964297a3b0SGarrett D'Amore errno = EINVAL;
3974297a3b0SGarrett D'Amore
3984297a3b0SGarrett D'Amore end_error:
3994297a3b0SGarrett D'Amore sverrno = errno;
4004297a3b0SGarrett D'Amore if (asciivalue != NULL)
4014297a3b0SGarrett D'Amore free(asciivalue);
4024297a3b0SGarrett D'Amore errno = sverrno;
4034297a3b0SGarrett D'Amore return (-1);
4044297a3b0SGarrett D'Amore }
4054297a3b0SGarrett D'Amore
406*2d08521bSGarrett D'Amore ssize_t
strfmon(char * _RESTRICT_KYWD s,size_t maxsize,const char * _RESTRICT_KYWD format,...)407*2d08521bSGarrett D'Amore strfmon(char *_RESTRICT_KYWD s, size_t maxsize,
408*2d08521bSGarrett D'Amore const char *_RESTRICT_KYWD format, ...)
4094297a3b0SGarrett D'Amore {
410*2d08521bSGarrett D'Amore va_list ap;
411*2d08521bSGarrett D'Amore ssize_t ret;
4124297a3b0SGarrett D'Amore
413*2d08521bSGarrett D'Amore va_start(ap, format);
414*2d08521bSGarrett D'Amore ret = strfmon_impl(s, maxsize, uselocale(NULL), format, ap);
415*2d08521bSGarrett D'Amore va_end(ap);
416*2d08521bSGarrett D'Amore return (ret);
4174297a3b0SGarrett D'Amore }
4184297a3b0SGarrett D'Amore
419*2d08521bSGarrett D'Amore ssize_t
strfmon_l(char * _RESTRICT_KYWD s,size_t maxsize,locale_t loc,const char * _RESTRICT_KYWD format,...)420*2d08521bSGarrett D'Amore strfmon_l(char *_RESTRICT_KYWD s, size_t maxsize, locale_t loc,
421*2d08521bSGarrett D'Amore const char *_RESTRICT_KYWD format, ...)
422*2d08521bSGarrett D'Amore {
423*2d08521bSGarrett D'Amore ssize_t ret;
424*2d08521bSGarrett D'Amore va_list ap;
425*2d08521bSGarrett D'Amore va_start(ap, format);
426*2d08521bSGarrett D'Amore ret = strfmon_impl(s, maxsize, loc, format, ap);
427*2d08521bSGarrett D'Amore va_end(ap);
428*2d08521bSGarrett D'Amore return (ret);
429*2d08521bSGarrett D'Amore }
430*2d08521bSGarrett D'Amore
431*2d08521bSGarrett D'Amore static void
setup_vars(const struct lc_monetary * lmon,int flags,char * cs_precedes,char * sep_by_space,char * sign_posn,const char ** signstr)432*2d08521bSGarrett D'Amore setup_vars(const struct lc_monetary *lmon, int flags, char *cs_precedes,
433*2d08521bSGarrett D'Amore char *sep_by_space, char *sign_posn, const char **signstr)
434*2d08521bSGarrett D'Amore {
435*2d08521bSGarrett D'Amore if ((flags & IS_NEGATIVE) && (flags & USE_INTL_CURRENCY)) {
436*2d08521bSGarrett D'Amore *cs_precedes = lmon->int_n_cs_precedes[0];
437*2d08521bSGarrett D'Amore *sep_by_space = lmon->int_n_sep_by_space[0];
438*2d08521bSGarrett D'Amore *sign_posn = (flags & PARENTH_POSN) ? 0 :
439*2d08521bSGarrett D'Amore lmon->int_n_sign_posn[0];
440*2d08521bSGarrett D'Amore *signstr = (lmon->negative_sign[0] == '\0') ? "-" :
441*2d08521bSGarrett D'Amore lmon->negative_sign;
442*2d08521bSGarrett D'Amore } else if (flags & USE_INTL_CURRENCY) {
443*2d08521bSGarrett D'Amore *cs_precedes = lmon->int_p_cs_precedes[0];
444*2d08521bSGarrett D'Amore *sep_by_space = lmon->int_p_sep_by_space[0];
445*2d08521bSGarrett D'Amore *sign_posn = (flags & PARENTH_POSN) ? 0 :
446*2d08521bSGarrett D'Amore lmon->int_p_sign_posn[0];
447*2d08521bSGarrett D'Amore *signstr = lmon->positive_sign;
448*2d08521bSGarrett D'Amore } else if (flags & IS_NEGATIVE) {
449*2d08521bSGarrett D'Amore *cs_precedes = lmon->n_cs_precedes[0];
450*2d08521bSGarrett D'Amore *sep_by_space = lmon->n_sep_by_space[0];
451*2d08521bSGarrett D'Amore *sign_posn = (flags & PARENTH_POSN) ? 0 : lmon->n_sign_posn[0];
452*2d08521bSGarrett D'Amore *signstr = (lmon->negative_sign[0] == '\0') ? "-" :
453*2d08521bSGarrett D'Amore lmon->negative_sign;
454*2d08521bSGarrett D'Amore } else {
455*2d08521bSGarrett D'Amore *cs_precedes = lmon->p_cs_precedes[0];
456*2d08521bSGarrett D'Amore *sep_by_space = lmon->p_sep_by_space[0];
457*2d08521bSGarrett D'Amore *sign_posn = (flags & PARENTH_POSN) ? 0 : lmon->p_sign_posn[0];
458*2d08521bSGarrett D'Amore *signstr = lmon->positive_sign;
459*2d08521bSGarrett D'Amore }
460*2d08521bSGarrett D'Amore
461*2d08521bSGarrett D'Amore /* Set default values for unspecified information. */
4624297a3b0SGarrett D'Amore if (*cs_precedes != 0)
4634297a3b0SGarrett D'Amore *cs_precedes = 1;
4644297a3b0SGarrett D'Amore if (*sep_by_space == CHAR_MAX)
4654297a3b0SGarrett D'Amore *sep_by_space = 0;
4664297a3b0SGarrett D'Amore if (*sign_posn == CHAR_MAX)
4674297a3b0SGarrett D'Amore *sign_posn = 0;
4684297a3b0SGarrett D'Amore }
4694297a3b0SGarrett D'Amore
4704297a3b0SGarrett D'Amore static int
calc_left_pad(const struct lc_monetary * lmon,int flags,const char * cur_symb)471*2d08521bSGarrett D'Amore calc_left_pad(const struct lc_monetary *lmon, int flags, const char *cur_symb)
4724297a3b0SGarrett D'Amore {
473*2d08521bSGarrett D'Amore char cs_precedes, sep_by_space, sign_posn;
474*2d08521bSGarrett D'Amore const char *signstr;
4754297a3b0SGarrett D'Amore int left_chars = 0;
4764297a3b0SGarrett D'Amore
477*2d08521bSGarrett D'Amore setup_vars(lmon, flags, &cs_precedes, &sep_by_space, &sign_posn,
478*2d08521bSGarrett D'Amore &signstr);
4794297a3b0SGarrett D'Amore
4804297a3b0SGarrett D'Amore if (cs_precedes != 0) {
4814297a3b0SGarrett D'Amore left_chars += strlen(cur_symb);
4824297a3b0SGarrett D'Amore if (sep_by_space != 0)
4834297a3b0SGarrett D'Amore left_chars++;
4844297a3b0SGarrett D'Amore }
4854297a3b0SGarrett D'Amore
4864297a3b0SGarrett D'Amore switch (sign_posn) {
4874297a3b0SGarrett D'Amore case 1:
4884297a3b0SGarrett D'Amore left_chars += strlen(signstr);
4894297a3b0SGarrett D'Amore break;
4904297a3b0SGarrett D'Amore case 3:
4914297a3b0SGarrett D'Amore case 4:
4924297a3b0SGarrett D'Amore if (cs_precedes != 0)
4934297a3b0SGarrett D'Amore left_chars += strlen(signstr);
4944297a3b0SGarrett D'Amore }
4954297a3b0SGarrett D'Amore return (left_chars);
4964297a3b0SGarrett D'Amore }
4974297a3b0SGarrett D'Amore
4984297a3b0SGarrett D'Amore static int
get_groups(int size,const char * grouping)499*2d08521bSGarrett D'Amore get_groups(int size, const char *grouping)
5004297a3b0SGarrett D'Amore {
5014297a3b0SGarrett D'Amore
5024297a3b0SGarrett D'Amore int chars = 0;
5034297a3b0SGarrett D'Amore
5044297a3b0SGarrett D'Amore if (*grouping == CHAR_MAX || *grouping <= 0) /* no grouping ? */
5054297a3b0SGarrett D'Amore return (0);
5064297a3b0SGarrett D'Amore
5074297a3b0SGarrett D'Amore while (size > (int)*grouping) {
5084297a3b0SGarrett D'Amore chars++;
5094297a3b0SGarrett D'Amore size -= (int)*grouping++;
5104297a3b0SGarrett D'Amore /* no more grouping ? */
5114297a3b0SGarrett D'Amore if (*grouping == CHAR_MAX)
5124297a3b0SGarrett D'Amore break;
5134297a3b0SGarrett D'Amore /* rest grouping with same value ? */
5144297a3b0SGarrett D'Amore if (*grouping == 0) {
5154297a3b0SGarrett D'Amore chars += (size - 1) / *(grouping - 1);
5164297a3b0SGarrett D'Amore break;
5174297a3b0SGarrett D'Amore }
5184297a3b0SGarrett D'Amore }
5194297a3b0SGarrett D'Amore return (chars);
5204297a3b0SGarrett D'Amore }
5214297a3b0SGarrett D'Amore
5224297a3b0SGarrett D'Amore /* convert double to ASCII */
5234297a3b0SGarrett D'Amore static char *
format_grouped_double(const struct lc_monetary * lmon,const struct lc_numeric * lnum,double value,int * flags,int left_prec,int right_prec,int pad_char)524*2d08521bSGarrett D'Amore format_grouped_double(const struct lc_monetary *lmon,
525*2d08521bSGarrett D'Amore const struct lc_numeric *lnum,
526*2d08521bSGarrett D'Amore double value, int *flags, int left_prec, int right_prec, int pad_char)
5274297a3b0SGarrett D'Amore {
5284297a3b0SGarrett D'Amore
5294297a3b0SGarrett D'Amore char *rslt;
5304297a3b0SGarrett D'Amore char *avalue;
5314297a3b0SGarrett D'Amore int avalue_size;
5324297a3b0SGarrett D'Amore char fmt[32];
5334297a3b0SGarrett D'Amore
5344297a3b0SGarrett D'Amore size_t bufsize;
5354297a3b0SGarrett D'Amore char *bufend;
5364297a3b0SGarrett D'Amore
5374297a3b0SGarrett D'Amore int padded;
5384297a3b0SGarrett D'Amore
539*2d08521bSGarrett D'Amore const char *grouping;
540*2d08521bSGarrett D'Amore const char *decimal_point;
541*2d08521bSGarrett D'Amore const char *thousands_sep;
542*2d08521bSGarrett D'Amore int decimal_len;
543*2d08521bSGarrett D'Amore int thousands_len;
5444297a3b0SGarrett D'Amore
5454297a3b0SGarrett D'Amore int groups = 0;
5464297a3b0SGarrett D'Amore
547*2d08521bSGarrett D'Amore grouping = lmon->mon_grouping;
548*2d08521bSGarrett D'Amore decimal_point = lmon->mon_decimal_point;
549*2d08521bSGarrett D'Amore if (*decimal_point == '\0')
550*2d08521bSGarrett D'Amore decimal_point = lnum->decimal_point;
551*2d08521bSGarrett D'Amore thousands_sep = lmon->mon_thousands_sep;
552*2d08521bSGarrett D'Amore if (*thousands_sep == '\0')
553*2d08521bSGarrett D'Amore thousands_sep = lnum->thousands_sep;
554*2d08521bSGarrett D'Amore
555*2d08521bSGarrett D'Amore decimal_len = strlen(decimal_point); /* usually 1 */
556*2d08521bSGarrett D'Amore thousands_len = strlen(thousands_sep); /* 0 or 1 usually */
5574297a3b0SGarrett D'Amore
5584297a3b0SGarrett D'Amore /* fill left_prec with default value */
5594297a3b0SGarrett D'Amore if (left_prec == -1)
5604297a3b0SGarrett D'Amore left_prec = 0;
5614297a3b0SGarrett D'Amore
5624297a3b0SGarrett D'Amore /* fill right_prec with default value */
5634297a3b0SGarrett D'Amore if (right_prec == -1) {
5644297a3b0SGarrett D'Amore if (*flags & USE_INTL_CURRENCY)
565*2d08521bSGarrett D'Amore right_prec = lmon->int_frac_digits[0];
5664297a3b0SGarrett D'Amore else
567*2d08521bSGarrett D'Amore right_prec = lmon->frac_digits[0];
5684297a3b0SGarrett D'Amore
5694297a3b0SGarrett D'Amore if (right_prec == CHAR_MAX) /* POSIX locale ? */
5704297a3b0SGarrett D'Amore right_prec = 2;
5714297a3b0SGarrett D'Amore }
5724297a3b0SGarrett D'Amore
5734297a3b0SGarrett D'Amore if (*flags & NEED_GROUPING)
5744297a3b0SGarrett D'Amore left_prec += get_groups(left_prec, grouping);
5754297a3b0SGarrett D'Amore
5764297a3b0SGarrett D'Amore /* convert to string */
5774297a3b0SGarrett D'Amore (void) snprintf(fmt, sizeof (fmt), "%%%d.%df",
5784297a3b0SGarrett D'Amore left_prec + right_prec + 1, right_prec);
5794297a3b0SGarrett D'Amore avalue_size = asprintf(&avalue, fmt, value);
5804297a3b0SGarrett D'Amore if (avalue_size < 0)
5814297a3b0SGarrett D'Amore return (NULL);
5824297a3b0SGarrett D'Amore
583*2d08521bSGarrett D'Amore /*
584*2d08521bSGarrett D'Amore * Make sure that we've enough space for result string.
585*2d08521bSGarrett D'Amore * This assumes that digits take up at least much space as
586*2d08521bSGarrett D'Amore * grouping and radix characters. The worst case currently known
587*2d08521bSGarrett D'Amore * is for Arabic, where two-byte UTF-8 sequences are used for both
588*2d08521bSGarrett D'Amore * decimal and thousands seperators, and groups can be a small as two
589*2d08521bSGarrett D'Amore * decimal digits. This will do no worse than doubling the storage
590*2d08521bSGarrett D'Amore * requirement.
591*2d08521bSGarrett D'Amore */
5924297a3b0SGarrett D'Amore bufsize = strlen(avalue)*2+1;
5934297a3b0SGarrett D'Amore rslt = calloc(1, bufsize);
5944297a3b0SGarrett D'Amore if (rslt == NULL) {
5954297a3b0SGarrett D'Amore free(avalue);
5964297a3b0SGarrett D'Amore return (NULL);
5974297a3b0SGarrett D'Amore }
5984297a3b0SGarrett D'Amore bufend = rslt + bufsize - 1; /* reserve space for trailing '\0' */
5994297a3b0SGarrett D'Amore
600*2d08521bSGarrett D'Amore /* skip spaces at beginning */
6014297a3b0SGarrett D'Amore padded = 0;
6024297a3b0SGarrett D'Amore while (avalue[padded] == ' ') {
6034297a3b0SGarrett D'Amore padded++;
6044297a3b0SGarrett D'Amore avalue_size--;
6054297a3b0SGarrett D'Amore }
6064297a3b0SGarrett D'Amore
6074297a3b0SGarrett D'Amore if (right_prec > 0) {
6084297a3b0SGarrett D'Amore bufend -= right_prec;
6094297a3b0SGarrett D'Amore (void) memcpy(bufend, avalue + avalue_size+padded-right_prec,
6104297a3b0SGarrett D'Amore right_prec);
611*2d08521bSGarrett D'Amore bufend -= decimal_len;
612*2d08521bSGarrett D'Amore (void) memcpy(bufend, decimal_point, decimal_len);
613*2d08521bSGarrett D'Amore avalue_size -= (right_prec + decimal_len);
6144297a3b0SGarrett D'Amore }
6154297a3b0SGarrett D'Amore
6164297a3b0SGarrett D'Amore if ((*flags & NEED_GROUPING) &&
617*2d08521bSGarrett D'Amore thousands_len != 0 &&
6184297a3b0SGarrett D'Amore *grouping != CHAR_MAX &&
6194297a3b0SGarrett D'Amore *grouping > 0) {
6204297a3b0SGarrett D'Amore while (avalue_size > (int)*grouping) {
6214297a3b0SGarrett D'Amore GRPCPY(*grouping);
6224297a3b0SGarrett D'Amore GRPSEP;
6234297a3b0SGarrett D'Amore grouping++;
6244297a3b0SGarrett D'Amore
6254297a3b0SGarrett D'Amore /* no more grouping ? */
6264297a3b0SGarrett D'Amore if (*grouping == CHAR_MAX)
6274297a3b0SGarrett D'Amore break;
6284297a3b0SGarrett D'Amore
6294297a3b0SGarrett D'Amore /* rest grouping with same value ? */
6304297a3b0SGarrett D'Amore if (*grouping == 0) {
6314297a3b0SGarrett D'Amore grouping--;
6324297a3b0SGarrett D'Amore while (avalue_size > *grouping) {
6334297a3b0SGarrett D'Amore GRPCPY(*grouping);
6344297a3b0SGarrett D'Amore GRPSEP;
6354297a3b0SGarrett D'Amore }
6364297a3b0SGarrett D'Amore }
6374297a3b0SGarrett D'Amore }
6384297a3b0SGarrett D'Amore if (avalue_size != 0)
6394297a3b0SGarrett D'Amore GRPCPY(avalue_size);
6404297a3b0SGarrett D'Amore padded -= groups;
6414297a3b0SGarrett D'Amore
6424297a3b0SGarrett D'Amore } else {
6434297a3b0SGarrett D'Amore bufend -= avalue_size;
6444297a3b0SGarrett D'Amore (void) memcpy(bufend, avalue+padded, avalue_size);
6454297a3b0SGarrett D'Amore if (right_prec == 0)
6464297a3b0SGarrett D'Amore padded--; /* decrease assumed $decimal_point */
6474297a3b0SGarrett D'Amore }
6484297a3b0SGarrett D'Amore
6494297a3b0SGarrett D'Amore /* do padding with pad_char */
6504297a3b0SGarrett D'Amore if (padded > 0) {
6514297a3b0SGarrett D'Amore bufend -= padded;
6524297a3b0SGarrett D'Amore (void) memset(bufend, pad_char, padded);
6534297a3b0SGarrett D'Amore }
6544297a3b0SGarrett D'Amore
6554297a3b0SGarrett D'Amore bufsize = bufsize - (bufend - rslt) + 1;
656eda71b4aSGarrett D'Amore (void) memmove(rslt, bufend, bufsize);
6574297a3b0SGarrett D'Amore free(avalue);
6584297a3b0SGarrett D'Amore return (rslt);
6594297a3b0SGarrett D'Amore }
660