.
[glibc/history.git] / stdlib / strfmon_l.c
blobeb7a17801ad8f525d594a28a4a746288ca2cd4c3
1 /* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996,1997,2002,2004,2006,2009 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
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.
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.
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. */
21 #include <ctype.h>
22 #include <errno.h>
23 #include <langinfo.h>
24 #include <locale.h>
25 #include <monetary.h>
26 #include "../libio/libioP.h"
27 #include "../libio/strfile.h"
28 #include <printf.h>
29 #include <stdarg.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include "../locale/localeinfo.h"
35 #define out_char(Ch) \
36 do { \
37 if (dest >= s + maxsize - 1) \
38 { \
39 __set_errno (E2BIG); \
40 va_end (ap); \
41 return -1; \
42 } \
43 *dest++ = (Ch); \
44 } while (0)
46 #define out_string(String) \
47 do { \
48 const char *_s = (String); \
49 while (*_s) \
50 out_char (*_s++); \
51 } while (0)
53 #define out_nstring(String, N) \
54 do { \
55 int _n = (N); \
56 const char *_s = (String); \
57 while (_n-- > 0) \
58 out_char (*_s++); \
59 } while (0)
61 #define to_digit(Ch) ((Ch) - '0')
64 /* We use this code also for the extended locale handling where the
65 function gets as an additional argument the locale which has to be
66 used. To access the values we have to redefine the _NL_CURRENT
67 macro. */
68 #undef _NL_CURRENT
69 #define _NL_CURRENT(category, item) \
70 (current->values[_NL_ITEM_INDEX (item)].string)
72 extern int __printf_fp (FILE *, const struct printf_info *,
73 const void *const *);
74 libc_hidden_proto (__printf_fp)
75 /* This function determines the number of digit groups in the output.
76 The definition is in printf_fp.c. */
77 extern unsigned int __guess_grouping (unsigned int intdig_max,
78 const char *grouping, wchar_t sepchar);
81 /* We have to overcome some problems with this implementation. On the
82 one hand the strfmon() function is specified in XPG4 and of course
83 it has to follow this. But on the other hand POSIX.2 specifies
84 some information in the LC_MONETARY category which should be used,
85 too. Some of the information contradicts the information which can
86 be specified in format string. */
87 ssize_t
88 __vstrfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format,
89 va_list ap)
91 struct locale_data *current = loc->__locales[LC_MONETARY];
92 _IO_strfile f;
93 struct printf_info info;
94 char *dest; /* Pointer so copy the output. */
95 const char *fmt; /* Pointer that walks through format. */
97 dest = s;
98 fmt = format;
100 /* Loop through the format-string. */
101 while (*fmt != '\0')
103 /* The floating-point value to output. */
104 union
106 double dbl;
107 __long_double_t ldbl;
109 fpnum;
110 int int_format;
111 int print_curr_symbol;
112 int left_prec;
113 int left_pad;
114 int right_prec;
115 int group;
116 char pad;
117 int is_long_double;
118 int p_sign_posn;
119 int n_sign_posn;
120 int sign_posn;
121 int other_sign_posn;
122 int left;
123 int is_negative;
124 int sep_by_space;
125 int other_sep_by_space;
126 int cs_precedes;
127 int other_cs_precedes;
128 const char *sign_string;
129 const char *other_sign_string;
130 int done;
131 const char *currency_symbol;
132 size_t currency_symbol_len;
133 long int width;
134 char *startp;
135 const void *ptr;
136 char space_char;
138 /* Process all character which do not introduce a format
139 specification. */
140 if (*fmt != '%')
142 out_char (*fmt++);
143 continue;
146 /* "%%" means a single '%' character. */
147 if (fmt[1] == '%')
149 out_char (*++fmt);
150 ++fmt;
151 continue;
154 /* Defaults for formatting. */
155 int_format = 0; /* Use international curr. symbol */
156 print_curr_symbol = 1; /* Print the currency symbol. */
157 left_prec = -1; /* No left precision specified. */
158 right_prec = -1; /* No right precision specified. */
159 group = 1; /* Print digits grouped. */
160 pad = ' '; /* Fill character is <SP>. */
161 is_long_double = 0; /* Double argument by default. */
162 p_sign_posn = -1; /* This indicates whether the */
163 n_sign_posn = -1; /* '(' flag is given. */
164 width = -1; /* No width specified so far. */
165 left = 0; /* Right justified by default. */
167 /* Parse group characters. */
168 while (1)
170 switch (*++fmt)
172 case '=': /* Set fill character. */
173 pad = *++fmt;
174 if (pad == '\0')
176 /* Premature EOS. */
177 __set_errno (EINVAL);
178 return -1;
180 continue;
181 case '^': /* Don't group digits. */
182 group = 0;
183 continue;
184 case '+': /* Use +/- for sign of number. */
185 if (n_sign_posn != -1)
187 __set_errno (EINVAL);
188 return -1;
190 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
191 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
192 continue;
193 case '(': /* Use ( ) for negative sign. */
194 if (n_sign_posn != -1)
196 __set_errno (EINVAL);
197 return -1;
199 p_sign_posn = 0;
200 n_sign_posn = 0;
201 continue;
202 case '!': /* Don't print the currency symbol. */
203 print_curr_symbol = 0;
204 continue;
205 case '-': /* Print left justified. */
206 left = 1;
207 continue;
208 default:
209 /* Will stop the loop. */;
211 break;
214 if (isdigit (*fmt))
216 /* Parse field width. */
217 width = to_digit (*fmt);
219 while (isdigit (*++fmt))
221 int val = to_digit (*fmt);
223 if (width > LONG_MAX / 10
224 || (width == LONG_MAX && val > LONG_MAX % 10))
226 __set_errno (E2BIG);
227 return -1;
230 width = width * 10 + val;
233 /* If we don't have enough room for the demanded width we
234 can stop now and return an error. */
235 if (width >= maxsize - (dest - s))
237 __set_errno (E2BIG);
238 return -1;
242 /* Recognize left precision. */
243 if (*fmt == '#')
245 if (!isdigit (*++fmt))
247 __set_errno (EINVAL);
248 return -1;
250 left_prec = to_digit (*fmt);
252 while (isdigit (*++fmt))
254 left_prec *= 10;
255 left_prec += to_digit (*fmt);
259 /* Recognize right precision. */
260 if (*fmt == '.')
262 if (!isdigit (*++fmt))
264 __set_errno (EINVAL);
265 return -1;
267 right_prec = to_digit (*fmt);
269 while (isdigit (*++fmt))
271 right_prec *= 10;
272 right_prec += to_digit (*fmt);
276 /* Handle modifier. This is an extension. */
277 if (*fmt == 'L')
279 ++fmt;
280 if (!__ldbl_is_dbl)
281 is_long_double = 1;
284 /* Handle format specifier. */
285 char int_symbol[4];
286 switch (*fmt++)
288 case 'i': { /* Use international currency symbol. */
289 const char *int_curr_symbol;
291 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
292 strncpy(int_symbol, int_curr_symbol, 3);
293 int_symbol[3] = '\0';
295 currency_symbol_len = 3;
296 currency_symbol = &int_symbol[0];
297 space_char = int_curr_symbol[3];
298 int_format = 1;
299 break;
301 case 'n': /* Use national currency symbol. */
302 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
303 currency_symbol_len = strlen (currency_symbol);
304 space_char = ' ';
305 int_format = 0;
306 break;
307 default: /* Any unrecognized format is an error. */
308 __set_errno (EINVAL);
309 return -1;
312 /* If not specified by the format string now find the values for
313 the format specification. */
314 if (p_sign_posn == -1)
315 p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
316 if (n_sign_posn == -1)
317 n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
319 if (right_prec == -1)
321 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
323 if (right_prec == CHAR_MAX)
324 right_prec = 2;
327 /* If we have to print the digits grouped determine how many
328 extra characters this means. */
329 if (group && left_prec != -1)
330 left_prec += __guess_grouping (left_prec,
331 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
332 *_NL_CURRENT (LC_MONETARY,
333 MON_THOUSANDS_SEP));
335 /* Now it's time to get the value. */
336 if (is_long_double == 1)
338 fpnum.ldbl = va_arg (ap, long double);
339 is_negative = fpnum.ldbl < 0;
340 if (is_negative)
341 fpnum.ldbl = -fpnum.ldbl;
343 else
345 fpnum.dbl = va_arg (ap, double);
346 is_negative = fpnum.dbl < 0;
347 if (is_negative)
348 fpnum.dbl = -fpnum.dbl;
351 /* We now know the sign of the value and can determine the format. */
352 if (is_negative)
354 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
355 /* If the locale does not specify a character for the
356 negative sign we use a '-'. */
357 if (*sign_string == '\0')
358 sign_string = (const char *) "-";
359 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
360 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
361 sign_posn = n_sign_posn;
363 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
364 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
365 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
366 other_sign_posn = p_sign_posn;
368 else
370 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
371 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
372 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
373 sign_posn = p_sign_posn;
375 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
376 if (*other_sign_string == '\0')
377 other_sign_string = (const char *) "-";
378 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
379 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
380 other_sign_posn = n_sign_posn;
383 /* Set default values for unspecified information. */
384 if (cs_precedes != 0)
385 cs_precedes = 1;
386 if (other_cs_precedes != 0)
387 other_cs_precedes = 1;
388 if (sep_by_space == CHAR_MAX)
389 sep_by_space = 0;
390 if (other_sep_by_space == CHAR_MAX)
391 other_sep_by_space = 0;
392 if (sign_posn == CHAR_MAX)
393 sign_posn = 1;
394 if (other_sign_posn == CHAR_MAX)
395 other_sign_posn = 1;
397 /* Check for degenerate cases */
398 if (sep_by_space == 2)
400 if (sign_posn == 0 ||
401 (sign_posn == 1 && !cs_precedes) ||
402 (sign_posn == 2 && cs_precedes))
403 /* sign and symbol are not adjacent, so no separator */
404 sep_by_space = 0;
406 if (other_sep_by_space == 2)
408 if (other_sign_posn == 0 ||
409 (other_sign_posn == 1 && !other_cs_precedes) ||
410 (other_sign_posn == 2 && other_cs_precedes))
411 /* sign and symbol are not adjacent, so no separator */
412 other_sep_by_space = 0;
415 /* Set the left precision and padding needed for alignment */
416 if (left_prec == -1)
418 left_prec = 0;
419 left_pad = 0;
421 else
423 /* Set left_pad to number of spaces needed to align positive
424 and negative formats */
426 int left_bytes = 0;
427 int other_left_bytes = 0;
429 /* Work out number of bytes for currency string and separator
430 preceding the value */
431 if (cs_precedes)
433 left_bytes += currency_symbol_len;
434 if (sep_by_space != 0)
435 ++left_bytes;
438 if (other_cs_precedes)
440 other_left_bytes += currency_symbol_len;
441 if (other_sep_by_space != 0)
442 ++other_left_bytes;
445 /* Work out number of bytes for the sign (or left parenthesis)
446 preceding the value */
447 if (sign_posn == 0 && is_negative)
448 ++left_bytes;
449 else if (sign_posn == 1)
450 left_bytes += strlen (sign_string);
451 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
452 left_bytes += strlen (sign_string);
454 if (other_sign_posn == 0 && !is_negative)
455 ++other_left_bytes;
456 else if (other_sign_posn == 1)
457 other_left_bytes += strlen (other_sign_string);
458 else if (other_cs_precedes &&
459 (other_sign_posn == 3 || other_sign_posn == 4))
460 other_left_bytes += strlen (other_sign_string);
462 /* Compare the number of bytes preceding the value for
463 each format, and set the padding accordingly */
464 if (other_left_bytes > left_bytes)
465 left_pad = other_left_bytes - left_bytes;
466 else
467 left_pad = 0;
470 /* Perhaps we'll someday make these things configurable so
471 better start using symbolic names now. */
472 #define left_paren '('
473 #define right_paren ')'
475 startp = dest; /* Remember start so we can compute length. */
477 while (left_pad-- > 0)
478 out_char (' ');
480 if (sign_posn == 0 && is_negative)
481 out_char (left_paren);
483 if (cs_precedes)
485 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
486 && sign_posn != 5)
488 out_string (sign_string);
489 if (sep_by_space == 2)
490 out_char (' ');
493 if (print_curr_symbol)
494 out_string (currency_symbol);
496 if (sign_posn == 4)
498 if (print_curr_symbol && sep_by_space == 2)
499 out_char (space_char);
500 out_string (sign_string);
501 if (sep_by_space == 1)
502 /* POSIX.2 and SUS are not clear on this case, but C99
503 says a space follows the adjacent-symbol-and-sign */
504 out_char (' ');
506 else
507 if (print_curr_symbol && sep_by_space == 1)
508 out_char (space_char);
510 else
511 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
512 && sign_posn != 4 && sign_posn != 5)
513 out_string (sign_string);
515 /* Print the number. */
516 #ifdef _IO_MTSAFE_IO
517 f._sbf._f._lock = NULL;
518 #endif
519 INTUSE(_IO_init) (&f._sbf._f, 0);
520 _IO_JUMPS (&f._sbf) = &_IO_str_jumps;
521 INTUSE(_IO_str_init_static) (&f, dest,
522 (s + maxsize) - dest, dest);
523 /* We clear the last available byte so we can find out whether
524 the numeric representation is too long. */
525 s[maxsize - 1] = '\0';
527 memset (&info, '\0', sizeof (info));
528 info.prec = right_prec;
529 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
530 info.spec = 'f';
531 info.is_long_double = is_long_double;
532 info.group = group;
533 info.pad = pad;
534 info.extra = 1; /* This means use values from LC_MONETARY. */
536 ptr = &fpnum;
537 done = __printf_fp (&f._sbf._f, &info, &ptr);
538 if (done < 0)
539 return -1;
541 if (s[maxsize - 1] != '\0')
543 __set_errno (E2BIG);
544 return -1;
547 dest += done;
549 if (!cs_precedes)
551 if (sign_posn == 3)
553 if (sep_by_space == 1)
554 out_char (' ');
555 out_string (sign_string);
558 if (print_curr_symbol)
560 if ((sign_posn == 3 && sep_by_space == 2)
561 || (sign_posn == 4 && sep_by_space == 1)
562 || (sign_posn == 2 && sep_by_space == 1)
563 || (sign_posn == 1 && sep_by_space == 1)
564 || (sign_posn == 0 && sep_by_space == 1))
565 out_char (space_char);
566 out_nstring (currency_symbol, currency_symbol_len);
569 if (sign_posn == 4)
571 if (sep_by_space == 2)
572 out_char (' ');
573 out_string (sign_string);
577 if (sign_posn == 2)
579 if (sep_by_space == 2)
580 out_char (' ');
581 out_string (sign_string);
584 if (sign_posn == 0 && is_negative)
585 out_char (right_paren);
587 /* Now test whether the output width is filled. */
588 if (dest - startp < width)
590 if (left)
591 /* We simply have to fill using spaces. */
593 out_char (' ');
594 while (dest - startp < width);
595 else
597 long int dist = width - (dest - startp);
598 for (char *cp = dest - 1; cp >= startp; --cp)
599 cp[dist] = cp[0];
601 dest += dist;
604 startp[--dist] = ' ';
605 while (dist > 0);
610 /* Terminate the string. */
611 *dest = '\0';
613 return dest - s;
616 ssize_t
617 ___strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
619 va_list ap;
621 va_start (ap, format);
623 ssize_t res = __vstrfmon_l (s, maxsize, loc, format, ap);
625 va_end (ap);
627 return res;
629 ldbl_strong_alias (___strfmon_l, __strfmon_l)
630 ldbl_weak_alias (___strfmon_l, strfmon_l)