Update.
[glibc.git] / stdlib / strfmon.c
blobeb4d0961f79358645d4dce898478f44684abaaa5
1 /* Formatting a monetary value according to the current locale.
2 Copyright (C) 1996,1997,1998,1999,2000,2001 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>
5 and Jochen Hein <Jochen.Hein@informatik.TU-Clausthal.de>, 1996.
7 The GNU C Library is free software; you can redistribute it and/or
8 modify it under the terms of the GNU Library General Public License as
9 published by the Free Software Foundation; either version 2 of the
10 License, or (at your option) any later version.
12 The GNU C Library is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Library General Public License for more details.
17 You should have received a copy of the GNU Library General Public
18 License along with the GNU C Library; see the file COPYING.LIB. If not,
19 write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20 Boston, MA 02111-1307, USA. */
22 #include <ctype.h>
23 #include <errno.h>
24 #include <langinfo.h>
25 #include <locale.h>
26 #include <monetary.h>
27 #ifdef USE_IN_LIBIO
28 # include "../libio/libioP.h"
29 # include "../libio/strfile.h"
30 #endif
31 #include <printf.h>
32 #include <stdarg.h>
33 #include <stdio.h>
34 #include <string.h>
35 #include "../locale/localeinfo.h"
38 #define out_char(Ch) \
39 do { \
40 if (dest >= s + maxsize - 1) \
41 { \
42 __set_errno (E2BIG); \
43 va_end (ap); \
44 return -1; \
45 } \
46 *dest++ = (Ch); \
47 } while (0)
49 #define out_string(String) \
50 do { \
51 const char *_s = (String); \
52 while (*_s) \
53 out_char (*_s++); \
54 } while (0)
56 #define out_nstring(String, N) \
57 do { \
58 int _n = (N); \
59 const char *_s = (String); \
60 while (_n-- > 0) \
61 out_char (*_s++); \
62 } while (0)
64 #define to_digit(Ch) ((Ch) - '0')
67 /* We use this code also for the extended locale handling where the
68 function gets as an additional argument the locale which has to be
69 used. To access the values we have to redefine the _NL_CURRENT
70 macro. */
71 #ifdef USE_IN_EXTENDED_LOCALE_MODEL
72 # undef _NL_CURRENT
73 # define _NL_CURRENT(category, item) \
74 (current->values[_NL_ITEM_INDEX (item)].string)
75 #endif
77 extern int __printf_fp (FILE *, const struct printf_info *,
78 const void *const *);
79 /* This function determines the number of digit groups in the output.
80 The definition is in printf_fp.c. */
81 extern unsigned int __guess_grouping (unsigned int intdig_max,
82 const char *grouping, wchar_t sepchar);
85 /* We have to overcome some problems with this implementation. On the
86 one hand the strfmon() function is specified in XPG4 and of course
87 it has to follow this. But on the other hand POSIX.2 specifies
88 some information in the LC_MONETARY category which should be used,
89 too. Some of the information contradicts the information which can
90 be specified in format string. */
91 #ifndef USE_IN_EXTENDED_LOCALE_MODEL
92 ssize_t
93 strfmon (char *s, size_t maxsize, const char *format, ...)
94 #else
95 ssize_t
96 __strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
97 #endif
99 #ifdef USE_IN_EXTENDED_LOCALE_MODEL
100 struct locale_data *current = loc->__locales[LC_MONETARY];
101 #endif
102 #ifdef USE_IN_LIBIO
103 _IO_strfile f;
104 #else
105 FILE f;
106 #endif
107 struct printf_info info;
108 va_list ap; /* Scan through the varargs. */
109 char *dest; /* Pointer so copy the output. */
110 const char *fmt; /* Pointer that walks through format. */
112 va_start (ap, format);
114 dest = s;
115 fmt = format;
117 /* Loop through the format-string. */
118 while (*fmt != '\0')
120 /* The floating-point value to output. */
121 union
123 double dbl;
124 __long_double_t ldbl;
126 fpnum;
127 int print_curr_symbol;
128 int left_prec;
129 int left_pad;
130 int right_prec;
131 int group;
132 char pad;
133 int is_long_double;
134 int p_sign_posn;
135 int n_sign_posn;
136 int sign_posn;
137 int other_sign_posn;
138 int left;
139 int is_negative;
140 int sep_by_space;
141 int other_sep_by_space;
142 int cs_precedes;
143 int other_cs_precedes;
144 const char *sign_string;
145 const char *other_sign_string;
146 int done;
147 const char *currency_symbol;
148 size_t currency_symbol_len;
149 int width;
150 char *startp;
151 const void *ptr;
152 char space_char;
154 /* Process all character which do not introduce a format
155 specification. */
156 if (*fmt != '%')
158 out_char (*fmt++);
159 continue;
162 /* "%%" means a single '%' character. */
163 if (fmt[1] == '%')
165 out_char (*++fmt);
166 ++fmt;
167 continue;
170 /* Defaults for formatting. */
171 print_curr_symbol = 1; /* Print the currency symbol. */
172 left_prec = -1; /* No left precision specified. */
173 right_prec = -1; /* No right precision specified. */
174 group = 1; /* Print digits grouped. */
175 pad = ' '; /* Fill character is <SP>. */
176 is_long_double = 0; /* Double argument by default. */
177 p_sign_posn = -1; /* This indicates whether the */
178 n_sign_posn = -1; /* '(' flag is given. */
179 width = -1; /* No width specified so far. */
180 left = 0; /* Right justified by default. */
182 /* Parse group characters. */
183 while (1)
185 switch (*++fmt)
187 case '=': /* Set fill character. */
188 pad = *++fmt;
189 if (pad == '\0')
191 /* Premature EOS. */
192 __set_errno (EINVAL);
193 va_end (ap);
194 return -1;
196 continue;
197 case '^': /* Don't group digits. */
198 group = 0;
199 continue;
200 case '+': /* Use +/- for sign of number. */
201 if (n_sign_posn != -1)
203 __set_errno (EINVAL);
204 va_end (ap);
205 return -1;
207 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
208 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
209 continue;
210 case '(': /* Use ( ) for negative sign. */
211 if (n_sign_posn != -1)
213 __set_errno (EINVAL);
214 va_end (ap);
215 return -1;
217 p_sign_posn = 0;
218 n_sign_posn = 0;
219 continue;
220 case '!': /* Don't print the currency symbol. */
221 print_curr_symbol = 0;
222 continue;
223 case '-': /* Print left justified. */
224 left = 1;
225 continue;
226 default:
227 /* Will stop the loop. */;
229 break;
232 /* If not specified by the format string now find the values for
233 the format specification. */
234 if (p_sign_posn == -1)
235 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
236 if (n_sign_posn == -1)
237 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
239 if (isdigit (*fmt))
241 /* Parse field width. */
242 width = to_digit (*fmt);
244 while (isdigit (*++fmt))
246 width *= 10;
247 width += to_digit (*fmt);
250 /* If we don't have enough room for the demanded width we
251 can stop now and return an error. */
252 if (dest + width >= s + maxsize)
254 __set_errno (E2BIG);
255 va_end (ap);
256 return -1;
260 /* Recognize left precision. */
261 if (*fmt == '#')
263 if (!isdigit (*++fmt))
265 __set_errno (EINVAL);
266 va_end (ap);
267 return -1;
269 left_prec = to_digit (*fmt);
271 while (isdigit (*++fmt))
273 left_prec *= 10;
274 left_prec += to_digit (*fmt);
278 /* Recognize right precision. */
279 if (*fmt == '.')
281 if (!isdigit (*++fmt))
283 __set_errno (EINVAL);
284 va_end (ap);
285 return -1;
287 right_prec = to_digit (*fmt);
289 while (isdigit (*++fmt))
291 right_prec *= 10;
292 right_prec += to_digit (*fmt);
296 /* Handle modifier. This is an extension. */
297 if (*fmt == 'L')
299 ++fmt;
300 is_long_double = 1;
303 /* Handle format specifier. */
304 switch (*fmt++)
306 case 'i': /* Use international currency symbol. */
307 currency_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
308 currency_symbol_len = 3;
309 space_char = currency_symbol[3];
310 if (right_prec == -1)
312 if (*_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS) == CHAR_MAX)
313 right_prec = 2;
314 else
315 right_prec = *_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS);
317 break;
318 case 'n': /* Use national currency symbol. */
319 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
320 currency_symbol_len = strlen (currency_symbol);
321 space_char = ' ';
322 if (right_prec == -1)
324 if (*_NL_CURRENT (LC_MONETARY, FRAC_DIGITS) == CHAR_MAX)
325 right_prec = 2;
326 else
327 right_prec = *_NL_CURRENT (LC_MONETARY, FRAC_DIGITS);
329 break;
330 default: /* Any unrecognized format is an error. */
331 __set_errno (EINVAL);
332 va_end (ap);
333 return -1;
336 /* If we have to print the digits grouped determine how many
337 extra characters this means. */
338 if (group && left_prec != -1)
339 left_prec += __guess_grouping (left_prec,
340 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
341 *_NL_CURRENT (LC_MONETARY,
342 MON_THOUSANDS_SEP));
344 /* Now it's time to get the value. */
345 if (is_long_double == 1)
347 fpnum.ldbl = va_arg (ap, long double);
348 is_negative = fpnum.ldbl < 0;
349 if (is_negative)
350 fpnum.ldbl = -fpnum.ldbl;
352 else
354 fpnum.dbl = va_arg (ap, double);
355 is_negative = fpnum.dbl < 0;
356 if (is_negative)
357 fpnum.dbl = -fpnum.dbl;
360 /* We now know the sign of the value and can determine the format. */
361 if (is_negative)
363 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
364 /* If the locale does not specify a character for the
365 negative sign we use a '-'. */
366 if (*sign_string == '\0')
367 sign_string = (const char *) "-";
368 cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
369 sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
370 sign_posn = n_sign_posn;
372 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
373 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
374 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
375 other_sign_posn = p_sign_posn;
377 else
379 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
380 cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
381 sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
382 sign_posn = p_sign_posn;
384 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
385 if (*other_sign_string == '\0')
386 other_sign_string = (const char *) "-";
387 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
388 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
389 other_sign_posn = n_sign_posn;
392 /* Set default values for unspecified information. */
393 if (cs_precedes != 0)
394 cs_precedes = 1;
395 if (other_cs_precedes != 0)
396 other_cs_precedes = 1;
397 if (sep_by_space == CHAR_MAX)
398 sep_by_space = 0;
399 if (other_sep_by_space == CHAR_MAX)
400 other_sep_by_space = 0;
401 if (sign_posn == CHAR_MAX)
402 sign_posn = 1;
403 if (other_sign_posn == CHAR_MAX)
404 other_sign_posn = 1;
406 /* Check for degenerate cases */
407 if (sep_by_space == 2)
409 if (sign_posn == 0 ||
410 (sign_posn == 1 && !cs_precedes) ||
411 (sign_posn == 2 && cs_precedes))
412 /* sign and symbol are not adjacent, so no separator */
413 sep_by_space = 0;
415 if (other_sep_by_space == 2)
417 if (other_sign_posn == 0 ||
418 (other_sign_posn == 1 && !other_cs_precedes) ||
419 (other_sign_posn == 2 && other_cs_precedes))
420 /* sign and symbol are not adjacent, so no separator */
421 other_sep_by_space = 0;
424 /* Set the left precision and padding needed for alignment */
425 if (left_prec == -1)
427 left_prec = 0;
428 left_pad = 0;
430 else
432 /* Set left_pad to number of spaces needed to align positive
433 and negative formats */
435 int left_bytes = 0;
436 int other_left_bytes = 0;
438 /* Work out number of bytes for currency string and separator
439 preceding the value */
440 if (cs_precedes)
442 left_bytes += currency_symbol_len;
443 if (sep_by_space != 0)
444 ++left_bytes;
447 if (other_cs_precedes)
449 other_left_bytes += currency_symbol_len;
450 if (other_sep_by_space != 0)
451 ++other_left_bytes;
454 /* Work out number of bytes for the sign (or left parenthesis)
455 preceding the value */
456 if (sign_posn == 0 && is_negative)
457 ++left_bytes;
458 else if (sign_posn == 1)
459 left_bytes += strlen (sign_string);
460 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
461 left_bytes += strlen (sign_string);
463 if (other_sign_posn == 0 && !is_negative)
464 ++other_left_bytes;
465 else if (other_sign_posn == 1)
466 other_left_bytes += strlen (other_sign_string);
467 else if (other_cs_precedes &&
468 (other_sign_posn == 3 || other_sign_posn == 4))
469 other_left_bytes += strlen (other_sign_string);
471 /* Compare the number of bytes preceding the value for
472 each format, and set the padding accordingly */
473 if (other_left_bytes > left_bytes)
474 left_pad = other_left_bytes - left_bytes;
475 else
476 left_pad = 0;
479 /* Perhaps we'll someday make these things configurable so
480 better start using symbolic names now. */
481 #define left_paren '('
482 #define right_paren ')'
484 startp = dest; /* Remember start so we can compute length. */
486 while (left_pad-- > 0)
487 out_char (' ');
489 if (sign_posn == 0 && is_negative)
490 out_char (left_paren);
492 if (cs_precedes)
494 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
495 && sign_posn != 5)
497 out_string (sign_string);
498 if (sep_by_space == 2)
499 out_char (' ');
502 if (print_curr_symbol)
504 out_string (currency_symbol);
506 if (sign_posn == 4)
508 if (sep_by_space == 2)
509 out_char (space_char);
510 out_string (sign_string);
511 if (sep_by_space == 1)
512 /* POSIX.2 and SUS are not clear on this case, but C99
513 says a space follows the adjacent-symbol-and-sign */
514 out_char (' ');
516 else
517 if (sep_by_space == 1)
518 out_char (space_char);
521 else
522 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
523 && sign_posn != 4 && sign_posn != 5)
524 out_string (sign_string);
526 /* Print the number. */
527 #ifdef USE_IN_LIBIO
528 _IO_init ((_IO_FILE *) &f, 0);
529 _IO_JUMPS ((struct _IO_FILE_plus *) &f) = &_IO_str_jumps;
530 _IO_str_init_static ((_IO_strfile *) &f, dest, (s + maxsize) - dest, dest);
531 #else
532 memset ((void *) &f, 0, sizeof (f));
533 f.__magic = _IOMAGIC;
534 f.__mode.__write = 1;
535 /* The buffer size is one less than MAXLEN
536 so we have space for the null terminator. */
537 f.__bufp = f.__buffer = (char *) dest;
538 f.__bufsize = (s + maxsize) - dest;
539 f.__put_limit = f.__buffer + f.__bufsize;
540 f.__get_limit = f.__buffer;
541 /* After the buffer is full (MAXLEN characters have been written),
542 any more characters written will go to the bit bucket. */
543 f.__room_funcs = __default_room_functions;
544 f.__io_funcs.__write = NULL;
545 f.__seen = 1;
546 #endif
547 /* We clear the last available byte so we can find out whether
548 the numeric representation is too long. */
549 s[maxsize - 1] = '\0';
551 info.prec = right_prec;
552 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
553 info.spec = 'f';
554 info.is_long_double = is_long_double;
555 info.is_short = 0;
556 info.is_long = 0;
557 info.alt = 0;
558 info.space = 0;
559 info.left = left;
560 info.showsign = 0;
561 info.group = group;
562 info.pad = pad;
563 info.extra = 1; /* This means use values from LC_MONETARY. */
564 info.wide = 0;
566 ptr = &fpnum;
567 done = __printf_fp ((FILE *) &f, &info, &ptr);
568 if (done < 0)
570 va_end (ap);
571 return -1;
574 if (s[maxsize - 1] != '\0')
576 __set_errno (E2BIG);
577 return -1;
580 dest += done;
582 if (!cs_precedes)
584 if (sign_posn == 3)
586 if (sep_by_space == 1)
587 out_char (' ');
588 out_string (sign_string);
591 if (print_curr_symbol)
593 if ((sign_posn == 3 && sep_by_space == 2)
594 || (sign_posn == 4 && sep_by_space == 1)
595 || (sign_posn == 2 && sep_by_space == 1)
596 || (sign_posn == 1 && sep_by_space == 1)
597 || (sign_posn == 0 && sep_by_space == 1))
598 out_char (space_char);
599 out_nstring (currency_symbol, currency_symbol_len);
600 if (sign_posn == 4)
602 if (sep_by_space == 2)
603 out_char (' ');
604 out_string (sign_string);
609 if (sign_posn == 2)
611 if (sep_by_space == 2)
612 out_char (' ');
613 out_string (sign_string);
616 if (sign_posn == 0 && is_negative)
617 out_char (right_paren);
619 /* Now test whether the output width is filled. */
620 if (dest - startp < width)
622 if (left)
623 /* We simply have to fill using spaces. */
625 out_char (' ');
626 while (dest - startp < width);
627 else
629 int dist = width - (dest - startp);
630 char *cp;
631 for (cp = dest - 1; cp >= startp; --cp)
632 cp[dist] = cp[0];
634 dest += dist;
637 startp[--dist] = ' ';
638 while (dist > 0);
643 /* Terminate the string. */
644 *dest = '\0';
646 va_end (ap);
648 return dest - s;