Update.
[glibc.git] / stdlib / strfmon.c
blobe2fe40b36b6886b5e89f09294c4c7bd90fa6ee4d
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 Lesser General Public
9 License as published by the Free Software Foundation; either
10 version 2.1 of the 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 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public
18 License along with the GNU C Library; if not, write to the Free
19 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
20 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 # ifdef _IO_MTSAFE_IO
105 _IO_lock_t lock;
106 # endif
107 #else
108 FILE f;
109 #endif
110 struct printf_info info;
111 va_list ap; /* Scan through the varargs. */
112 char *dest; /* Pointer so copy the output. */
113 const char *fmt; /* Pointer that walks through format. */
115 va_start (ap, format);
117 dest = s;
118 fmt = format;
120 /* Loop through the format-string. */
121 while (*fmt != '\0')
123 /* The floating-point value to output. */
124 union
126 double dbl;
127 __long_double_t ldbl;
129 fpnum;
130 int print_curr_symbol;
131 int left_prec;
132 int left_pad;
133 int right_prec;
134 int group;
135 char pad;
136 int is_long_double;
137 int p_sign_posn;
138 int n_sign_posn;
139 int sign_posn;
140 int other_sign_posn;
141 int left;
142 int is_negative;
143 int sep_by_space;
144 int other_sep_by_space;
145 int cs_precedes;
146 int other_cs_precedes;
147 const char *sign_string;
148 const char *other_sign_string;
149 int done;
150 const char *currency_symbol;
151 size_t currency_symbol_len;
152 int width;
153 char *startp;
154 const void *ptr;
155 char space_char;
157 /* Process all character which do not introduce a format
158 specification. */
159 if (*fmt != '%')
161 out_char (*fmt++);
162 continue;
165 /* "%%" means a single '%' character. */
166 if (fmt[1] == '%')
168 out_char (*++fmt);
169 ++fmt;
170 continue;
173 /* Defaults for formatting. */
174 print_curr_symbol = 1; /* Print the currency symbol. */
175 left_prec = -1; /* No left precision specified. */
176 right_prec = -1; /* No right precision specified. */
177 group = 1; /* Print digits grouped. */
178 pad = ' '; /* Fill character is <SP>. */
179 is_long_double = 0; /* Double argument by default. */
180 p_sign_posn = -1; /* This indicates whether the */
181 n_sign_posn = -1; /* '(' flag is given. */
182 width = -1; /* No width specified so far. */
183 left = 0; /* Right justified by default. */
185 /* Parse group characters. */
186 while (1)
188 switch (*++fmt)
190 case '=': /* Set fill character. */
191 pad = *++fmt;
192 if (pad == '\0')
194 /* Premature EOS. */
195 __set_errno (EINVAL);
196 va_end (ap);
197 return -1;
199 continue;
200 case '^': /* Don't group digits. */
201 group = 0;
202 continue;
203 case '+': /* Use +/- for sign of number. */
204 if (n_sign_posn != -1)
206 __set_errno (EINVAL);
207 va_end (ap);
208 return -1;
210 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
211 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
212 continue;
213 case '(': /* Use ( ) for negative sign. */
214 if (n_sign_posn != -1)
216 __set_errno (EINVAL);
217 va_end (ap);
218 return -1;
220 p_sign_posn = 0;
221 n_sign_posn = 0;
222 continue;
223 case '!': /* Don't print the currency symbol. */
224 print_curr_symbol = 0;
225 continue;
226 case '-': /* Print left justified. */
227 left = 1;
228 continue;
229 default:
230 /* Will stop the loop. */;
232 break;
235 /* If not specified by the format string now find the values for
236 the format specification. */
237 if (p_sign_posn == -1)
238 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
239 if (n_sign_posn == -1)
240 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
242 if (isdigit (*fmt))
244 /* Parse field width. */
245 width = to_digit (*fmt);
247 while (isdigit (*++fmt))
249 width *= 10;
250 width += to_digit (*fmt);
253 /* If we don't have enough room for the demanded width we
254 can stop now and return an error. */
255 if (dest + width >= s + maxsize)
257 __set_errno (E2BIG);
258 va_end (ap);
259 return -1;
263 /* Recognize left precision. */
264 if (*fmt == '#')
266 if (!isdigit (*++fmt))
268 __set_errno (EINVAL);
269 va_end (ap);
270 return -1;
272 left_prec = to_digit (*fmt);
274 while (isdigit (*++fmt))
276 left_prec *= 10;
277 left_prec += to_digit (*fmt);
281 /* Recognize right precision. */
282 if (*fmt == '.')
284 if (!isdigit (*++fmt))
286 __set_errno (EINVAL);
287 va_end (ap);
288 return -1;
290 right_prec = to_digit (*fmt);
292 while (isdigit (*++fmt))
294 right_prec *= 10;
295 right_prec += to_digit (*fmt);
299 /* Handle modifier. This is an extension. */
300 if (*fmt == 'L')
302 ++fmt;
303 is_long_double = 1;
306 /* Handle format specifier. */
307 switch (*fmt++)
309 case 'i': /* Use international currency symbol. */
310 currency_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
311 currency_symbol_len = 3;
312 space_char = currency_symbol[3];
313 if (right_prec == -1)
315 if (*_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS) == CHAR_MAX)
316 right_prec = 2;
317 else
318 right_prec = *_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS);
320 break;
321 case 'n': /* Use national currency symbol. */
322 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
323 currency_symbol_len = strlen (currency_symbol);
324 space_char = ' ';
325 if (right_prec == -1)
327 if (*_NL_CURRENT (LC_MONETARY, FRAC_DIGITS) == CHAR_MAX)
328 right_prec = 2;
329 else
330 right_prec = *_NL_CURRENT (LC_MONETARY, FRAC_DIGITS);
332 break;
333 default: /* Any unrecognized format is an error. */
334 __set_errno (EINVAL);
335 va_end (ap);
336 return -1;
339 /* If we have to print the digits grouped determine how many
340 extra characters this means. */
341 if (group && left_prec != -1)
342 left_prec += __guess_grouping (left_prec,
343 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
344 *_NL_CURRENT (LC_MONETARY,
345 MON_THOUSANDS_SEP));
347 /* Now it's time to get the value. */
348 if (is_long_double == 1)
350 fpnum.ldbl = va_arg (ap, long double);
351 is_negative = fpnum.ldbl < 0;
352 if (is_negative)
353 fpnum.ldbl = -fpnum.ldbl;
355 else
357 fpnum.dbl = va_arg (ap, double);
358 is_negative = fpnum.dbl < 0;
359 if (is_negative)
360 fpnum.dbl = -fpnum.dbl;
363 /* We now know the sign of the value and can determine the format. */
364 if (is_negative)
366 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
367 /* If the locale does not specify a character for the
368 negative sign we use a '-'. */
369 if (*sign_string == '\0')
370 sign_string = (const char *) "-";
371 cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
372 sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
373 sign_posn = n_sign_posn;
375 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
376 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
377 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
378 other_sign_posn = p_sign_posn;
380 else
382 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
383 cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
384 sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
385 sign_posn = p_sign_posn;
387 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
388 if (*other_sign_string == '\0')
389 other_sign_string = (const char *) "-";
390 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
391 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
392 other_sign_posn = n_sign_posn;
395 /* Set default values for unspecified information. */
396 if (cs_precedes != 0)
397 cs_precedes = 1;
398 if (other_cs_precedes != 0)
399 other_cs_precedes = 1;
400 if (sep_by_space == CHAR_MAX)
401 sep_by_space = 0;
402 if (other_sep_by_space == CHAR_MAX)
403 other_sep_by_space = 0;
404 if (sign_posn == CHAR_MAX)
405 sign_posn = 1;
406 if (other_sign_posn == CHAR_MAX)
407 other_sign_posn = 1;
409 /* Check for degenerate cases */
410 if (sep_by_space == 2)
412 if (sign_posn == 0 ||
413 (sign_posn == 1 && !cs_precedes) ||
414 (sign_posn == 2 && cs_precedes))
415 /* sign and symbol are not adjacent, so no separator */
416 sep_by_space = 0;
418 if (other_sep_by_space == 2)
420 if (other_sign_posn == 0 ||
421 (other_sign_posn == 1 && !other_cs_precedes) ||
422 (other_sign_posn == 2 && other_cs_precedes))
423 /* sign and symbol are not adjacent, so no separator */
424 other_sep_by_space = 0;
427 /* Set the left precision and padding needed for alignment */
428 if (left_prec == -1)
430 left_prec = 0;
431 left_pad = 0;
433 else
435 /* Set left_pad to number of spaces needed to align positive
436 and negative formats */
438 int left_bytes = 0;
439 int other_left_bytes = 0;
441 /* Work out number of bytes for currency string and separator
442 preceding the value */
443 if (cs_precedes)
445 left_bytes += currency_symbol_len;
446 if (sep_by_space != 0)
447 ++left_bytes;
450 if (other_cs_precedes)
452 other_left_bytes += currency_symbol_len;
453 if (other_sep_by_space != 0)
454 ++other_left_bytes;
457 /* Work out number of bytes for the sign (or left parenthesis)
458 preceding the value */
459 if (sign_posn == 0 && is_negative)
460 ++left_bytes;
461 else if (sign_posn == 1)
462 left_bytes += strlen (sign_string);
463 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
464 left_bytes += strlen (sign_string);
466 if (other_sign_posn == 0 && !is_negative)
467 ++other_left_bytes;
468 else if (other_sign_posn == 1)
469 other_left_bytes += strlen (other_sign_string);
470 else if (other_cs_precedes &&
471 (other_sign_posn == 3 || other_sign_posn == 4))
472 other_left_bytes += strlen (other_sign_string);
474 /* Compare the number of bytes preceding the value for
475 each format, and set the padding accordingly */
476 if (other_left_bytes > left_bytes)
477 left_pad = other_left_bytes - left_bytes;
478 else
479 left_pad = 0;
482 /* Perhaps we'll someday make these things configurable so
483 better start using symbolic names now. */
484 #define left_paren '('
485 #define right_paren ')'
487 startp = dest; /* Remember start so we can compute length. */
489 while (left_pad-- > 0)
490 out_char (' ');
492 if (sign_posn == 0 && is_negative)
493 out_char (left_paren);
495 if (cs_precedes)
497 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
498 && sign_posn != 5)
500 out_string (sign_string);
501 if (sep_by_space == 2)
502 out_char (' ');
505 if (print_curr_symbol)
507 out_string (currency_symbol);
509 if (sign_posn == 4)
511 if (sep_by_space == 2)
512 out_char (space_char);
513 out_string (sign_string);
514 if (sep_by_space == 1)
515 /* POSIX.2 and SUS are not clear on this case, but C99
516 says a space follows the adjacent-symbol-and-sign */
517 out_char (' ');
519 else
520 if (sep_by_space == 1)
521 out_char (space_char);
524 else
525 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
526 && sign_posn != 4 && sign_posn != 5)
527 out_string (sign_string);
529 /* Print the number. */
530 #ifdef USE_IN_LIBIO
531 # ifdef _IO_MTSAFE_IO
532 f._sbf._f._lock = &lock;
533 # endif
534 _IO_init ((_IO_FILE *) &f, 0);
535 _IO_JUMPS ((struct _IO_FILE_plus *) &f) = &_IO_str_jumps;
536 _IO_str_init_static ((_IO_strfile *) &f, dest, (s + maxsize) - dest, dest);
537 #else
538 memset ((void *) &f, 0, sizeof (f));
539 f.__magic = _IOMAGIC;
540 f.__mode.__write = 1;
541 /* The buffer size is one less than MAXLEN
542 so we have space for the null terminator. */
543 f.__bufp = f.__buffer = (char *) dest;
544 f.__bufsize = (s + maxsize) - dest;
545 f.__put_limit = f.__buffer + f.__bufsize;
546 f.__get_limit = f.__buffer;
547 /* After the buffer is full (MAXLEN characters have been written),
548 any more characters written will go to the bit bucket. */
549 f.__room_funcs = __default_room_functions;
550 f.__io_funcs.__write = NULL;
551 f.__seen = 1;
552 #endif
553 /* We clear the last available byte so we can find out whether
554 the numeric representation is too long. */
555 s[maxsize - 1] = '\0';
557 info.prec = right_prec;
558 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
559 info.spec = 'f';
560 info.is_long_double = is_long_double;
561 info.is_short = 0;
562 info.is_long = 0;
563 info.alt = 0;
564 info.space = 0;
565 info.left = left;
566 info.showsign = 0;
567 info.group = group;
568 info.pad = pad;
569 info.extra = 1; /* This means use values from LC_MONETARY. */
570 info.wide = 0;
572 ptr = &fpnum;
573 done = __printf_fp ((FILE *) &f, &info, &ptr);
574 if (done < 0)
576 va_end (ap);
577 return -1;
580 if (s[maxsize - 1] != '\0')
582 __set_errno (E2BIG);
583 return -1;
586 dest += done;
588 if (!cs_precedes)
590 if (sign_posn == 3)
592 if (sep_by_space == 1)
593 out_char (' ');
594 out_string (sign_string);
597 if (print_curr_symbol)
599 if ((sign_posn == 3 && sep_by_space == 2)
600 || (sign_posn == 4 && sep_by_space == 1)
601 || (sign_posn == 2 && sep_by_space == 1)
602 || (sign_posn == 1 && sep_by_space == 1)
603 || (sign_posn == 0 && sep_by_space == 1))
604 out_char (space_char);
605 out_nstring (currency_symbol, currency_symbol_len);
606 if (sign_posn == 4)
608 if (sep_by_space == 2)
609 out_char (' ');
610 out_string (sign_string);
615 if (sign_posn == 2)
617 if (sep_by_space == 2)
618 out_char (' ');
619 out_string (sign_string);
622 if (sign_posn == 0 && is_negative)
623 out_char (right_paren);
625 /* Now test whether the output width is filled. */
626 if (dest - startp < width)
628 if (left)
629 /* We simply have to fill using spaces. */
631 out_char (' ');
632 while (dest - startp < width);
633 else
635 int dist = width - (dest - startp);
636 char *cp;
637 for (cp = dest - 1; cp >= startp; --cp)
638 cp[dist] = cp[0];
640 dest += dist;
643 startp[--dist] = ' ';
644 while (dist > 0);
649 /* Terminate the string. */
650 *dest = '\0';
652 va_end (ap);
654 return dest - s;