New file.
[glibc.git] / stdlib / strfmon.c
blob49d88834fe35dea28e865d37d6092a9d48f186c1
1 /* Formatting a monetary value according to the current locale.
2 Copyright (C) 1996, 1997, 1998, 1999, 2000 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 <monetary.h>
26 #ifdef USE_IN_LIBIO
27 # include "../libio/libioP.h"
28 # include "../libio/strfile.h"
29 #endif
30 #include <printf.h>
31 #include <stdarg.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include "../locale/localeinfo.h"
37 #define out_char(Ch) \
38 do { \
39 if (dest >= s + maxsize - 1) \
40 { \
41 __set_errno (E2BIG); \
42 va_end (ap); \
43 return -1; \
44 } \
45 *dest++ = (Ch); \
46 } while (0)
48 #define out_string(String) \
49 do { \
50 const char *_s = (String); \
51 while (*_s) \
52 out_char (*_s++); \
53 } while (0)
55 #define out_nstring(String, N) \
56 do { \
57 int _n = (N); \
58 const char *_s = (String); \
59 while (_n-- > 0) \
60 out_char (*_s++); \
61 } while (0)
63 #define to_digit(Ch) ((Ch) - '0')
66 /* We use this code also for the extended locale handling where the
67 function gets as an additional argument the locale which has to be
68 used. To access the values we have to redefine the _NL_CURRENT
69 macro. */
70 #ifdef USE_IN_EXTENDED_LOCALE_MODEL
71 # undef _NL_CURRENT
72 # define _NL_CURRENT(category, item) \
73 (current->values[_NL_ITEM_INDEX (item)].string)
74 #endif
76 extern int __printf_fp (FILE *, const struct printf_info *,
77 const void *const *);
78 /* This function determines the number of digit groups in the output.
79 The definition is in printf_fp.c. */
80 extern unsigned int __guess_grouping (unsigned int intdig_max,
81 const char *grouping, wchar_t sepchar);
84 /* We have to overcome some problems with this implementation. On the
85 one hand the strfmon() function is specified in XPG4 and of course
86 it has to follow this. But on the other hand POSIX.2 specifies
87 some information in the LC_MONETARY category which should be used,
88 too. Some of the information contradicts the information which can
89 be specified in format string. */
90 #ifndef USE_IN_EXTENDED_LOCALE_MODEL
91 ssize_t
92 strfmon (char *s, size_t maxsize, const char *format, ...)
93 #else
94 ssize_t
95 __strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
96 #endif
98 #ifdef USE_IN_EXTENDED_LOCALE_MODEL
99 struct locale_data *current = loc->__locales[LC_MONETARY];
100 #endif
101 #ifdef USE_IN_LIBIO
102 _IO_strfile f;
103 #else
104 FILE f;
105 #endif
106 struct printf_info info;
107 va_list ap; /* Scan through the varargs. */
108 char *dest; /* Pointer so copy the output. */
109 const char *fmt; /* Pointer that walks through format. */
111 va_start (ap, format);
113 dest = s;
114 fmt = format;
116 /* Loop through the format-string. */
117 while (*fmt != '\0')
119 /* The floating-point value to output. */
120 union
122 double dbl;
123 __long_double_t ldbl;
125 fpnum;
126 int print_curr_symbol;
127 int left_prec;
128 int left_pad;
129 int right_prec;
130 int group;
131 char pad;
132 int is_long_double;
133 int p_sign_posn;
134 int n_sign_posn;
135 int sign_posn;
136 int other_sign_posn;
137 int left;
138 int is_negative;
139 int sep_by_space;
140 int other_sep_by_space;
141 int cs_precedes;
142 int other_cs_precedes;
143 const char *sign_string;
144 const char *other_sign_string;
145 int done;
146 const char *currency_symbol;
147 size_t currency_symbol_len;
148 int width;
149 char *startp;
150 const void *ptr;
151 char space_char;
153 /* Process all character which do not introduce a format
154 specification. */
155 if (*fmt != '%')
157 out_char (*fmt++);
158 continue;
161 /* "%%" means a single '%' character. */
162 if (fmt[1] == '%')
164 out_char (*++fmt);
165 ++fmt;
166 continue;
169 /* Defaults for formatting. */
170 print_curr_symbol = 1; /* Print the currency symbol. */
171 left_prec = -1; /* No left precision specified. */
172 right_prec = -1; /* No right precision specified. */
173 group = 1; /* Print digits grouped. */
174 pad = ' '; /* Fill character is <SP>. */
175 is_long_double = 0; /* Double argument by default. */
176 p_sign_posn = -1; /* This indicates whether the */
177 n_sign_posn = -1; /* '(' flag is given. */
178 width = -1; /* No width specified so far. */
179 left = 0; /* Right justified by default. */
181 /* Parse group characters. */
182 while (1)
184 switch (*++fmt)
186 case '=': /* Set fill character. */
187 pad = *++fmt;
188 if (pad == '\0')
190 /* Premature EOS. */
191 __set_errno (EINVAL);
192 va_end (ap);
193 return -1;
195 continue;
196 case '^': /* Don't group digits. */
197 group = 0;
198 continue;
199 case '+': /* Use +/- for sign of number. */
200 if (n_sign_posn != -1)
202 __set_errno (EINVAL);
203 va_end (ap);
204 return -1;
206 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
207 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
208 continue;
209 case '(': /* Use ( ) for negative sign. */
210 if (n_sign_posn != -1)
212 __set_errno (EINVAL);
213 va_end (ap);
214 return -1;
216 p_sign_posn = 0;
217 n_sign_posn = 0;
218 continue;
219 case '!': /* Don't print the currency symbol. */
220 print_curr_symbol = 0;
221 continue;
222 case '-': /* Print left justified. */
223 left = 1;
224 continue;
225 default:
226 /* Will stop the loop. */;
228 break;
231 /* If not specified by the format string now find the values for
232 the format specification. */
233 if (p_sign_posn == -1)
234 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
235 if (n_sign_posn == -1)
236 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
238 if (isdigit (*fmt))
240 /* Parse field width. */
241 width = to_digit (*fmt);
243 while (isdigit (*++fmt))
245 width *= 10;
246 width += to_digit (*fmt);
249 /* If we don't have enough room for the demanded width we
250 can stop now and return an error. */
251 if (dest + width >= s + maxsize)
253 __set_errno (E2BIG);
254 va_end (ap);
255 return -1;
259 /* Recognize left precision. */
260 if (*fmt == '#')
262 if (!isdigit (*++fmt))
264 __set_errno (EINVAL);
265 va_end (ap);
266 return -1;
268 left_prec = to_digit (*fmt);
270 while (isdigit (*++fmt))
272 left_prec *= 10;
273 left_prec += to_digit (*fmt);
277 /* Recognize right precision. */
278 if (*fmt == '.')
280 if (!isdigit (*++fmt))
282 __set_errno (EINVAL);
283 va_end (ap);
284 return -1;
286 right_prec = to_digit (*fmt);
288 while (isdigit (*++fmt))
290 right_prec *= 10;
291 right_prec += to_digit (*fmt);
295 /* Handle modifier. This is an extension. */
296 if (*fmt == 'L')
298 ++fmt;
299 is_long_double = 1;
302 /* Handle format specifier. */
303 switch (*fmt++)
305 case 'i': /* Use international currency symbol. */
306 currency_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
307 currency_symbol_len = 3;
308 space_char = currency_symbol[3];
309 if (right_prec == -1)
311 if (*_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS) == CHAR_MAX)
312 right_prec = 2;
313 else
314 right_prec = *_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS);
316 break;
317 case 'n': /* Use national currency symbol. */
318 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
319 currency_symbol_len = strlen (currency_symbol);
320 space_char = ' ';
321 if (right_prec == -1)
323 if (*_NL_CURRENT (LC_MONETARY, FRAC_DIGITS) == CHAR_MAX)
324 right_prec = 2;
325 else
326 right_prec = *_NL_CURRENT (LC_MONETARY, FRAC_DIGITS);
328 break;
329 default: /* Any unrecognized format is an error. */
330 __set_errno (EINVAL);
331 va_end (ap);
332 return -1;
335 /* If we have to print the digits grouped determine how many
336 extra characters this means. */
337 if (group && left_prec != -1)
338 left_prec += __guess_grouping (left_prec,
339 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
340 *_NL_CURRENT (LC_MONETARY,
341 MON_THOUSANDS_SEP));
343 /* Now it's time to get the value. */
344 if (is_long_double == 1)
346 fpnum.ldbl = va_arg (ap, long double);
347 is_negative = fpnum.ldbl < 0;
348 if (is_negative)
349 fpnum.ldbl = -fpnum.ldbl;
351 else
353 fpnum.dbl = va_arg (ap, double);
354 is_negative = fpnum.dbl < 0;
355 if (is_negative)
356 fpnum.dbl = -fpnum.dbl;
359 /* We now know the sign of the value and can determine the format. */
360 if (is_negative)
362 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
363 /* If the locale does not specify a character for the
364 negative sign we use a '-'. */
365 if (*sign_string == '\0')
366 sign_string = (const char *) "-";
367 cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
368 sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
369 sign_posn = n_sign_posn;
371 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
372 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
373 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
374 other_sign_posn = p_sign_posn;
376 else
378 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
379 cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
380 sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
381 sign_posn = p_sign_posn;
383 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
384 if (*other_sign_string == '\0')
385 other_sign_string = (const char *) "-";
386 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
387 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
388 other_sign_posn = n_sign_posn;
391 /* Set default values for unspecified information. */
392 if (cs_precedes != 0)
393 cs_precedes = 1;
394 if (other_cs_precedes != 0)
395 other_cs_precedes = 1;
396 if (sep_by_space == CHAR_MAX)
397 sep_by_space = 0;
398 if (other_sep_by_space == CHAR_MAX)
399 other_sep_by_space = 0;
400 if (sign_posn == CHAR_MAX)
401 sign_posn = 1;
402 if (other_sign_posn == CHAR_MAX)
403 other_sign_posn = 1;
405 /* Check for degenerate cases */
406 if (sep_by_space == 2)
408 if (sign_posn == 0 ||
409 (sign_posn == 1 && !cs_precedes) ||
410 (sign_posn == 2 && cs_precedes))
411 /* sign and symbol are not adjacent, so no separator */
412 sep_by_space = 0;
414 if (other_sep_by_space == 2)
416 if (other_sign_posn == 0 ||
417 (other_sign_posn == 1 && !other_cs_precedes) ||
418 (other_sign_posn == 2 && other_cs_precedes))
419 /* sign and symbol are not adjacent, so no separator */
420 other_sep_by_space = 0;
423 /* Set the left precision and padding needed for alignment */
424 if (left_prec == -1)
426 left_prec = 0;
427 left_pad = 0;
429 else
431 /* Set left_pad to number of spaces needed to align positive
432 and negative formats */
434 int left_bytes = 0;
435 int other_left_bytes = 0;
437 /* Work out number of bytes for currency string and separator
438 preceding the value */
439 if (cs_precedes)
441 left_bytes += currency_symbol_len;
442 if (sep_by_space != 0)
443 ++left_bytes;
446 if (other_cs_precedes)
448 other_left_bytes += currency_symbol_len;
449 if (other_sep_by_space != 0)
450 ++other_left_bytes;
453 /* Work out number of bytes for the sign (or left parenthesis)
454 preceding the value */
455 if (sign_posn == 0 && is_negative)
456 ++left_bytes;
457 else if (sign_posn == 1)
458 left_bytes += strlen (sign_string);
459 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
460 left_bytes += strlen (sign_string);
462 if (other_sign_posn == 0 && !is_negative)
463 ++other_left_bytes;
464 else if (other_sign_posn == 1)
465 other_left_bytes += strlen (other_sign_string);
466 else if (other_cs_precedes &&
467 (other_sign_posn == 3 || other_sign_posn == 4))
468 other_left_bytes += strlen (other_sign_string);
470 /* Compare the number of bytes preceding the value for
471 each format, and set the padding accordingly */
472 if (other_left_bytes > left_bytes)
473 left_pad = other_left_bytes - left_bytes;
474 else
475 left_pad = 0;
478 /* Perhaps we'll someday make these things configurable so
479 better start using symbolic names now. */
480 #define left_paren '('
481 #define right_paren ')'
483 startp = dest; /* Remember start so we can compute length. */
485 while (left_pad-- > 0)
486 out_char (' ');
488 if (sign_posn == 0 && is_negative)
489 out_char (left_paren);
491 if (cs_precedes)
493 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
494 && sign_posn != 5)
496 out_string (sign_string);
497 if (sep_by_space == 2)
498 out_char (' ');
501 if (print_curr_symbol)
503 out_string (currency_symbol);
505 if (sign_posn == 4)
507 if (sep_by_space == 2)
508 out_char (space_char);
509 out_string (sign_string);
510 if (sep_by_space == 1)
511 /* POSIX.2 and SUS are not clear on this case, but C99
512 says a space follows the adjacent-symbol-and-sign */
513 out_char (' ');
515 else
516 if (sep_by_space == 1)
517 out_char (space_char);
520 else
521 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
522 && sign_posn != 4 && sign_posn != 5)
523 out_string (sign_string);
525 /* Print the number. */
526 #ifdef USE_IN_LIBIO
527 _IO_init ((_IO_FILE *) &f, 0);
528 _IO_JUMPS ((struct _IO_FILE_plus *) &f) = &_IO_str_jumps;
529 _IO_str_init_static ((_IO_strfile *) &f, dest, (s + maxsize) - dest, dest);
530 #else
531 memset ((void *) &f, 0, sizeof (f));
532 f.__magic = _IOMAGIC;
533 f.__mode.__write = 1;
534 /* The buffer size is one less than MAXLEN
535 so we have space for the null terminator. */
536 f.__bufp = f.__buffer = (char *) dest;
537 f.__bufsize = (s + maxsize) - dest;
538 f.__put_limit = f.__buffer + f.__bufsize;
539 f.__get_limit = f.__buffer;
540 /* After the buffer is full (MAXLEN characters have been written),
541 any more characters written will go to the bit bucket. */
542 f.__room_funcs = __default_room_functions;
543 f.__io_funcs.__write = NULL;
544 f.__seen = 1;
545 #endif
546 /* We clear the last available byte so we can find out whether
547 the numeric representation is too long. */
548 s[maxsize - 1] = '\0';
550 info.prec = right_prec;
551 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
552 info.spec = 'f';
553 info.is_long_double = is_long_double;
554 info.is_short = 0;
555 info.is_long = 0;
556 info.alt = 0;
557 info.space = 0;
558 info.left = left;
559 info.showsign = 0;
560 info.group = group;
561 info.pad = pad;
562 info.extra = 1; /* This means use values from LC_MONETARY. */
563 info.wide = 0;
565 ptr = &fpnum;
566 done = __printf_fp ((FILE *) &f, &info, &ptr);
567 if (done < 0)
569 va_end (ap);
570 return -1;
573 if (s[maxsize - 1] != '\0')
575 __set_errno (E2BIG);
576 return -1;
579 dest += done;
581 if (!cs_precedes)
583 if (sign_posn == 3)
585 if (sep_by_space == 1)
586 out_char (' ');
587 out_string (sign_string);
590 if (print_curr_symbol)
592 if ((sign_posn == 3 && sep_by_space == 2)
593 || (sign_posn == 4 && sep_by_space == 1)
594 || (sign_posn == 2 && sep_by_space == 1)
595 || (sign_posn == 1 && sep_by_space == 1)
596 || (sign_posn == 0 && sep_by_space == 1))
597 out_char (space_char);
598 out_nstring (currency_symbol, currency_symbol_len);
599 if (sign_posn == 4)
601 if (sep_by_space == 2)
602 out_char (' ');
603 out_string (sign_string);
608 if (sign_posn == 2)
610 if (sep_by_space == 2)
611 out_char (' ');
612 out_string (sign_string);
615 if (sign_posn == 0 && is_negative)
616 out_char (right_paren);
618 /* Now test whether the output width is filled. */
619 if (dest - startp < width)
621 if (left)
622 /* We simply have to fill using spaces. */
624 out_char (' ');
625 while (dest - startp < width);
626 else
628 int dist = width - (dest - startp);
629 char *cp;
630 for (cp = dest - 1; cp >= startp; --cp)
631 cp[dist] = cp[0];
633 dest += dist;
636 startp[--dist] = ' ';
637 while (dist > 0);
642 /* Terminate the string. */
643 *dest = '\0';
645 va_end (ap);
647 return dest - s;