2007-11-18 Roland McGrath <roland@frob.com>
[glibc.git] / stdlib / strfmon_l.c
blobc9f3a47b41d27a6c393ffbda86328c8ca569e623
1 /* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996, 1997, 2002, 2004, 2006 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 #ifdef _IO_MTSAFE_IO
94 _IO_lock_t lock;
95 #endif
96 struct printf_info info;
97 char *dest; /* Pointer so copy the output. */
98 const char *fmt; /* Pointer that walks through format. */
100 dest = s;
101 fmt = format;
103 /* Loop through the format-string. */
104 while (*fmt != '\0')
106 /* The floating-point value to output. */
107 union
109 double dbl;
110 __long_double_t ldbl;
112 fpnum;
113 int int_format;
114 int print_curr_symbol;
115 int left_prec;
116 int left_pad;
117 int right_prec;
118 int group;
119 char pad;
120 int is_long_double;
121 int p_sign_posn;
122 int n_sign_posn;
123 int sign_posn;
124 int other_sign_posn;
125 int left;
126 int is_negative;
127 int sep_by_space;
128 int other_sep_by_space;
129 int cs_precedes;
130 int other_cs_precedes;
131 const char *sign_string;
132 const char *other_sign_string;
133 int done;
134 const char *currency_symbol;
135 size_t currency_symbol_len;
136 int width;
137 char *startp;
138 const void *ptr;
139 char space_char;
141 /* Process all character which do not introduce a format
142 specification. */
143 if (*fmt != '%')
145 out_char (*fmt++);
146 continue;
149 /* "%%" means a single '%' character. */
150 if (fmt[1] == '%')
152 out_char (*++fmt);
153 ++fmt;
154 continue;
157 /* Defaults for formatting. */
158 int_format = 0; /* Use international curr. symbol */
159 print_curr_symbol = 1; /* Print the currency symbol. */
160 left_prec = -1; /* No left precision specified. */
161 right_prec = -1; /* No right precision specified. */
162 group = 1; /* Print digits grouped. */
163 pad = ' '; /* Fill character is <SP>. */
164 is_long_double = 0; /* Double argument by default. */
165 p_sign_posn = -1; /* This indicates whether the */
166 n_sign_posn = -1; /* '(' flag is given. */
167 width = -1; /* No width specified so far. */
168 left = 0; /* Right justified by default. */
170 /* Parse group characters. */
171 while (1)
173 switch (*++fmt)
175 case '=': /* Set fill character. */
176 pad = *++fmt;
177 if (pad == '\0')
179 /* Premature EOS. */
180 __set_errno (EINVAL);
181 return -1;
183 continue;
184 case '^': /* Don't group digits. */
185 group = 0;
186 continue;
187 case '+': /* Use +/- for sign of number. */
188 if (n_sign_posn != -1)
190 __set_errno (EINVAL);
191 return -1;
193 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
194 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
195 continue;
196 case '(': /* Use ( ) for negative sign. */
197 if (n_sign_posn != -1)
199 __set_errno (EINVAL);
200 return -1;
202 p_sign_posn = 0;
203 n_sign_posn = 0;
204 continue;
205 case '!': /* Don't print the currency symbol. */
206 print_curr_symbol = 0;
207 continue;
208 case '-': /* Print left justified. */
209 left = 1;
210 continue;
211 default:
212 /* Will stop the loop. */;
214 break;
217 if (isdigit (*fmt))
219 /* Parse field width. */
220 width = to_digit (*fmt);
222 while (isdigit (*++fmt))
224 width *= 10;
225 width += to_digit (*fmt);
228 /* If we don't have enough room for the demanded width we
229 can stop now and return an error. */
230 if (dest + width >= s + maxsize)
232 __set_errno (E2BIG);
233 return -1;
237 /* Recognize left precision. */
238 if (*fmt == '#')
240 if (!isdigit (*++fmt))
242 __set_errno (EINVAL);
243 return -1;
245 left_prec = to_digit (*fmt);
247 while (isdigit (*++fmt))
249 left_prec *= 10;
250 left_prec += to_digit (*fmt);
254 /* Recognize right precision. */
255 if (*fmt == '.')
257 if (!isdigit (*++fmt))
259 __set_errno (EINVAL);
260 return -1;
262 right_prec = to_digit (*fmt);
264 while (isdigit (*++fmt))
266 right_prec *= 10;
267 right_prec += to_digit (*fmt);
271 /* Handle modifier. This is an extension. */
272 if (*fmt == 'L')
274 ++fmt;
275 if (!__ldbl_is_dbl)
276 is_long_double = 1;
279 /* Handle format specifier. */
280 char int_symbol[4];
281 switch (*fmt++)
283 case 'i': { /* Use international currency symbol. */
284 const char *int_curr_symbol;
286 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
287 strncpy(int_symbol, int_curr_symbol, 3);
288 int_symbol[3] = '\0';
290 currency_symbol_len = 3;
291 currency_symbol = &int_symbol[0];
292 space_char = int_curr_symbol[3];
293 int_format = 1;
294 break;
296 case 'n': /* Use national currency symbol. */
297 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
298 currency_symbol_len = strlen (currency_symbol);
299 space_char = ' ';
300 int_format = 0;
301 break;
302 default: /* Any unrecognized format is an error. */
303 __set_errno (EINVAL);
304 return -1;
307 /* If not specified by the format string now find the values for
308 the format specification. */
309 if (p_sign_posn == -1)
310 p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
311 if (n_sign_posn == -1)
312 n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
314 if (right_prec == -1)
316 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
318 if (right_prec == CHAR_MAX)
319 right_prec = 2;
322 /* If we have to print the digits grouped determine how many
323 extra characters this means. */
324 if (group && left_prec != -1)
325 left_prec += __guess_grouping (left_prec,
326 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
327 *_NL_CURRENT (LC_MONETARY,
328 MON_THOUSANDS_SEP));
330 /* Now it's time to get the value. */
331 if (is_long_double == 1)
333 fpnum.ldbl = va_arg (ap, long double);
334 is_negative = fpnum.ldbl < 0;
335 if (is_negative)
336 fpnum.ldbl = -fpnum.ldbl;
338 else
340 fpnum.dbl = va_arg (ap, double);
341 is_negative = fpnum.dbl < 0;
342 if (is_negative)
343 fpnum.dbl = -fpnum.dbl;
346 /* We now know the sign of the value and can determine the format. */
347 if (is_negative)
349 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
350 /* If the locale does not specify a character for the
351 negative sign we use a '-'. */
352 if (*sign_string == '\0')
353 sign_string = (const char *) "-";
354 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
355 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
356 sign_posn = n_sign_posn;
358 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
359 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
360 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
361 other_sign_posn = p_sign_posn;
363 else
365 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
366 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
367 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
368 sign_posn = p_sign_posn;
370 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
371 if (*other_sign_string == '\0')
372 other_sign_string = (const char *) "-";
373 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
374 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
375 other_sign_posn = n_sign_posn;
378 /* Set default values for unspecified information. */
379 if (cs_precedes != 0)
380 cs_precedes = 1;
381 if (other_cs_precedes != 0)
382 other_cs_precedes = 1;
383 if (sep_by_space == CHAR_MAX)
384 sep_by_space = 0;
385 if (other_sep_by_space == CHAR_MAX)
386 other_sep_by_space = 0;
387 if (sign_posn == CHAR_MAX)
388 sign_posn = 1;
389 if (other_sign_posn == CHAR_MAX)
390 other_sign_posn = 1;
392 /* Check for degenerate cases */
393 if (sep_by_space == 2)
395 if (sign_posn == 0 ||
396 (sign_posn == 1 && !cs_precedes) ||
397 (sign_posn == 2 && cs_precedes))
398 /* sign and symbol are not adjacent, so no separator */
399 sep_by_space = 0;
401 if (other_sep_by_space == 2)
403 if (other_sign_posn == 0 ||
404 (other_sign_posn == 1 && !other_cs_precedes) ||
405 (other_sign_posn == 2 && other_cs_precedes))
406 /* sign and symbol are not adjacent, so no separator */
407 other_sep_by_space = 0;
410 /* Set the left precision and padding needed for alignment */
411 if (left_prec == -1)
413 left_prec = 0;
414 left_pad = 0;
416 else
418 /* Set left_pad to number of spaces needed to align positive
419 and negative formats */
421 int left_bytes = 0;
422 int other_left_bytes = 0;
424 /* Work out number of bytes for currency string and separator
425 preceding the value */
426 if (cs_precedes)
428 left_bytes += currency_symbol_len;
429 if (sep_by_space != 0)
430 ++left_bytes;
433 if (other_cs_precedes)
435 other_left_bytes += currency_symbol_len;
436 if (other_sep_by_space != 0)
437 ++other_left_bytes;
440 /* Work out number of bytes for the sign (or left parenthesis)
441 preceding the value */
442 if (sign_posn == 0 && is_negative)
443 ++left_bytes;
444 else if (sign_posn == 1)
445 left_bytes += strlen (sign_string);
446 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
447 left_bytes += strlen (sign_string);
449 if (other_sign_posn == 0 && !is_negative)
450 ++other_left_bytes;
451 else if (other_sign_posn == 1)
452 other_left_bytes += strlen (other_sign_string);
453 else if (other_cs_precedes &&
454 (other_sign_posn == 3 || other_sign_posn == 4))
455 other_left_bytes += strlen (other_sign_string);
457 /* Compare the number of bytes preceding the value for
458 each format, and set the padding accordingly */
459 if (other_left_bytes > left_bytes)
460 left_pad = other_left_bytes - left_bytes;
461 else
462 left_pad = 0;
465 /* Perhaps we'll someday make these things configurable so
466 better start using symbolic names now. */
467 #define left_paren '('
468 #define right_paren ')'
470 startp = dest; /* Remember start so we can compute length. */
472 while (left_pad-- > 0)
473 out_char (' ');
475 if (sign_posn == 0 && is_negative)
476 out_char (left_paren);
478 if (cs_precedes)
480 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
481 && sign_posn != 5)
483 out_string (sign_string);
484 if (sep_by_space == 2)
485 out_char (' ');
488 if (print_curr_symbol)
489 out_string (currency_symbol);
491 if (sign_posn == 4)
493 if (print_curr_symbol && sep_by_space == 2)
494 out_char (space_char);
495 out_string (sign_string);
496 if (sep_by_space == 1)
497 /* POSIX.2 and SUS are not clear on this case, but C99
498 says a space follows the adjacent-symbol-and-sign */
499 out_char (' ');
501 else
502 if (print_curr_symbol && sep_by_space == 1)
503 out_char (space_char);
505 else
506 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
507 && sign_posn != 4 && sign_posn != 5)
508 out_string (sign_string);
510 /* Print the number. */
511 #ifdef _IO_MTSAFE_IO
512 f._sbf._f._lock = &lock;
513 #endif
514 INTUSE(_IO_init) ((_IO_FILE *) &f, 0);
515 _IO_JUMPS ((struct _IO_FILE_plus *) &f) = &_IO_str_jumps;
516 INTUSE(_IO_str_init_static) ((_IO_strfile *) &f, dest,
517 (s + maxsize) - dest, dest);
518 /* We clear the last available byte so we can find out whether
519 the numeric representation is too long. */
520 s[maxsize - 1] = '\0';
522 memset (&info, '\0', sizeof (info));
523 info.prec = right_prec;
524 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
525 info.spec = 'f';
526 info.is_long_double = is_long_double;
527 info.group = group;
528 info.pad = pad;
529 info.extra = 1; /* This means use values from LC_MONETARY. */
531 ptr = &fpnum;
532 done = __printf_fp ((FILE *) &f, &info, &ptr);
533 if (done < 0)
534 return -1;
536 if (s[maxsize - 1] != '\0')
538 __set_errno (E2BIG);
539 return -1;
542 dest += done;
544 if (!cs_precedes)
546 if (sign_posn == 3)
548 if (sep_by_space == 1)
549 out_char (' ');
550 out_string (sign_string);
553 if (print_curr_symbol)
555 if ((sign_posn == 3 && sep_by_space == 2)
556 || (sign_posn == 4 && sep_by_space == 1)
557 || (sign_posn == 2 && sep_by_space == 1)
558 || (sign_posn == 1 && sep_by_space == 1)
559 || (sign_posn == 0 && sep_by_space == 1))
560 out_char (space_char);
561 out_nstring (currency_symbol, currency_symbol_len);
564 if (sign_posn == 4)
566 if (sep_by_space == 2)
567 out_char (' ');
568 out_string (sign_string);
572 if (sign_posn == 2)
574 if (sep_by_space == 2)
575 out_char (' ');
576 out_string (sign_string);
579 if (sign_posn == 0 && is_negative)
580 out_char (right_paren);
582 /* Now test whether the output width is filled. */
583 if (dest - startp < width)
585 if (left)
586 /* We simply have to fill using spaces. */
588 out_char (' ');
589 while (dest - startp < width);
590 else
592 int dist = width - (dest - startp);
593 char *cp;
594 for (cp = dest - 1; cp >= startp; --cp)
595 cp[dist] = cp[0];
597 dest += dist;
600 startp[--dist] = ' ';
601 while (dist > 0);
606 /* Terminate the string. */
607 *dest = '\0';
609 return dest - s;
612 ssize_t
613 ___strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
615 va_list ap;
617 va_start (ap, format);
619 ssize_t res = __vstrfmon_l (s, maxsize, loc, format, ap);
621 va_end (ap);
623 return res;
625 ldbl_strong_alias (___strfmon_l, __strfmon_l)
626 ldbl_weak_alias (___strfmon_l, strfmon_l)