Update.
[glibc.git] / stdlib / strfmon.c
blob187e33fe37951138abd065e5341b280a3e039e43
1 /* Formatting a monetary value according to the current locale.
2 Copyright (C) 1996, 1997, 1998 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 to_digit(Ch) ((Ch) - '0')
58 /* We use this code also for the extended locale handling where the
59 function gets as an additional argument the locale which has to be
60 used. To access the values we have to redefine the _NL_CURRENT
61 macro. */
62 #ifdef USE_IN_EXTENDED_LOCALE_MODEL
63 # undef _NL_CURRENT
64 # define _NL_CURRENT(category, item) \
65 (current->values[_NL_ITEM_INDEX (item)].string)
66 #endif
68 extern int __printf_fp (FILE *, const struct printf_info *,
69 const void **const);
70 /* This function determines the number of digit groups in the output.
71 The definition is in printf_fp.c. */
72 extern unsigned int __guess_grouping (unsigned int intdig_max,
73 const char *grouping, wchar_t sepchar);
76 /* We have to overcome some problems with this implementation. On the
77 one hand the strfmon() function is specified in XPG4 and of course
78 it has to follow this. But on the other hand POSIX.2 specifies
79 some information in the LC_MONETARY category which should be used,
80 too. Some of the information contradicts the information which can
81 be specified in format string. */
82 #ifndef USE_IN_EXTENDED_LOCALE_MODEL
83 ssize_t
84 strfmon (char *s, size_t maxsize, const char *format, ...)
85 #else
86 ssize_t
87 __strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
88 #endif
90 #ifdef USE_IN_EXTENDED_LOCALE_MODEL
91 struct locale_data *current = loc->__locales[LC_MONETARY];
92 #endif
93 #ifdef USE_IN_LIBIO
94 _IO_strfile f;
95 #else
96 FILE f;
97 #endif
98 struct printf_info info;
99 va_list ap; /* Scan through the varargs. */
100 char *dest; /* Pointer so copy the output. */
101 const char *fmt; /* Pointer that walks through format. */
103 va_start (ap, format);
105 dest = s;
106 fmt = format;
108 /* Loop through the format-string. */
109 while (*fmt != '\0')
111 /* The floating-point value to output. */
112 union
114 double dbl;
115 __long_double_t ldbl;
117 fpnum;
118 int print_curr_symbol;
119 int left_prec;
120 int right_prec;
121 int group;
122 char pad;
123 int is_long_double;
124 int p_sign_posn;
125 int n_sign_posn;
126 int sign_posn;
127 int left;
128 int is_negative;
129 int sep_by_space;
130 int cs_precedes;
131 char sign_char;
132 int done;
133 const char *currency_symbol;
134 int width;
135 char *startp;
136 const void *ptr;
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 print_curr_symbol = 1; /* Print the currency symbol. */
156 left_prec = -1; /* No left precision specified. */
157 right_prec = -1; /* No right precision specified. */
158 group = 1; /* Print digits grouped. */
159 pad = ' '; /* Fill character is <SP>. */
160 is_long_double = 0; /* Double argument by default. */
161 p_sign_posn = -1; /* This indicates whether the */
162 n_sign_posn = -1; /* '(' flag is given. */
163 width = -1; /* No width specified so far. */
164 left = 0; /* Right justified by default. */
166 /* Parse group characters. */
167 while (1)
169 switch (*++fmt)
171 case '=': /* Set fill character. */
172 pad = *++fmt;
173 if (pad == '\0')
175 /* Premature EOS. */
176 __set_errno (EINVAL);
177 va_end (ap);
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 va_end (ap);
189 return -1;
191 if (*_NL_CURRENT (LC_MONETARY, P_SIGN_POSN) == '\0')
192 p_sign_posn = 1;
193 else
194 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
195 if (*_NL_CURRENT (LC_MONETARY, N_SIGN_POSN) == '\0')
196 n_sign_posn = 1;
197 else
198 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
199 continue;
200 case '(': /* Use ( ) for negative sign. */
201 if (n_sign_posn != -1)
203 __set_errno (EINVAL);
204 va_end (ap);
205 return -1;
207 p_sign_posn = 0;
208 n_sign_posn = 0;
209 continue;
210 case '!': /* Don't print the currency symbol. */
211 print_curr_symbol = 0;
212 continue;
213 case '-': /* Print left justified. */
214 left = 1;
215 continue;
216 default:
217 /* Will stop the loop. */;
219 break;
222 /* If not specified by the format string now find the values for
223 the format specification. */
224 if (p_sign_posn == -1)
225 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
226 if (n_sign_posn == -1)
227 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
229 if (isdigit (*fmt))
231 /* Parse field width. */
232 width = to_digit (*fmt);
234 while (isdigit (*++fmt))
236 width *= 10;
237 width += to_digit (*fmt);
240 /* If we don't have enough room for the demanded width we
241 can stop now and return an error. */
242 if (dest + width >= s + maxsize)
244 __set_errno (E2BIG);
245 va_end (ap);
246 return -1;
250 /* Recognize left precision. */
251 if (*fmt == '#')
253 if (!isdigit (*++fmt))
255 __set_errno (EINVAL);
256 va_end (ap);
257 return -1;
259 left_prec = to_digit (*fmt);
261 while (isdigit (*++fmt))
263 left_prec *= 10;
264 left_prec += to_digit (*fmt);
268 /* Recognize right precision. */
269 if (*fmt == '.')
271 if (!isdigit (*++fmt))
273 __set_errno (EINVAL);
274 va_end (ap);
275 return -1;
277 right_prec = to_digit (*fmt);
279 while (isdigit (*++fmt))
281 right_prec *= 10;
282 right_prec += to_digit (*fmt);
286 /* Handle modifier. This is an extension. */
287 if (*fmt == 'L')
289 ++fmt;
290 is_long_double = 1;
293 /* Handle format specifier. */
294 switch (*fmt++)
296 case 'i': /* Use international currency symbol. */
297 currency_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
298 if (right_prec == -1)
300 if (*_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS) == CHAR_MAX)
301 right_prec = 2;
302 else
303 right_prec = *_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS);
305 break;
306 case 'n': /* Use national currency symbol. */
307 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
308 if (right_prec == -1)
310 if (*_NL_CURRENT (LC_MONETARY, FRAC_DIGITS) == CHAR_MAX)
311 right_prec = 2;
312 else
313 right_prec = *_NL_CURRENT (LC_MONETARY, FRAC_DIGITS);
315 break;
316 default: /* Any unrecognized format is an error. */
317 __set_errno (EINVAL);
318 va_end (ap);
319 return -1;
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_char = *_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_char == '\0')
353 sign_char = '-';
354 cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
355 sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
356 sign_posn = n_sign_posn;
358 else
360 sign_char = *_NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
361 /* If the locale does not specify a character for the
362 positive sign we use a <SP>. */
363 if (sign_char == '\0')
364 sign_char = ' ';
365 cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
366 sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
367 sign_posn = p_sign_posn;
370 /* Set default values for unspecified information. */
371 if (cs_precedes != 0)
372 cs_precedes = 1;
373 if (sep_by_space == 127)
374 sep_by_space = 0;
375 if (left_prec == -1)
376 left_prec = 0;
379 /* Perhaps we'll someday make these things configurable so
380 better start using symbolic names now. */
381 #define left_paren '('
382 #define right_paren ')'
384 startp = dest; /* Remember start so we can compute length. */
386 if (sign_posn == 0)
387 out_char (is_negative ? left_paren : ' ');
389 if (cs_precedes)
391 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
392 && sign_posn != 5)
394 out_char (sign_char);
395 if (sep_by_space == 2)
396 out_char (' ');
399 if (print_curr_symbol)
401 out_string (currency_symbol);
403 if (sign_posn == 4)
405 if (sep_by_space == 2)
406 out_char (' ');
407 out_char (sign_char);
409 else
410 if (sep_by_space == 1)
411 out_char (' ');
414 else
415 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
416 && sign_posn != 4 && sign_posn != 5)
417 out_char (sign_char);
419 /* Print the number. */
420 #ifdef USE_IN_LIBIO
421 _IO_init ((_IO_FILE *) &f, 0);
422 _IO_JUMPS ((_IO_FILE *) &f) = &_IO_str_jumps;
423 _IO_str_init_static ((_IO_FILE *) &f, dest, (s + maxsize) - dest, dest);
424 #else
425 memset((void *) &f, 0, sizeof(f));
426 f.__magic = _IOMAGIC;
427 f.__mode.__write = 1;
428 /* The buffer size is one less than MAXLEN
429 so we have space for the null terminator. */
430 f.__bufp = f.__buffer = (char *) dest;
431 f.__bufsize = (s + maxsize) - dest;
432 f.__put_limit = f.__buffer + f.__bufsize;
433 f.__get_limit = f.__buffer;
434 /* After the buffer is full (MAXLEN characters have been written),
435 any more characters written will go to the bit bucket. */
436 f.__room_funcs = __default_room_functions;
437 f.__io_funcs.__write = NULL;
438 f.__seen = 1;
439 #endif
440 /* We clear the last available byte so we can find out whether
441 the numeric representation is too long. */
442 s[maxsize - 1] = '\0';
444 info.prec = right_prec;
445 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
446 info.spec = 'f';
447 info.is_long_double = is_long_double;
448 info.is_short = 0;
449 info.is_long = 0;
450 info.alt = 0;
451 info.space = 0;
452 info.left = left;
453 info.showsign = 0;
454 info.group = group;
455 info.pad = pad;
456 info.extra = 1; /* This means use values from LC_MONETARY. */
458 ptr = &fpnum;
459 done = __printf_fp ((FILE *) &f, &info, &ptr);
460 if (done < 0)
462 va_end (ap);
463 return -1;
466 if (s[maxsize - 1] != '\0')
467 return -1;
469 dest += done;
471 if (!cs_precedes)
473 if (sign_posn == 3)
475 if (sep_by_space == 1)
476 out_char (' ');
477 out_char (sign_char);
480 if (print_curr_symbol)
482 if (sign_posn == 3 && sep_by_space == 2)
483 out_char (' ');
484 out_string (currency_symbol);
487 else
488 if (sign_posn == 2)
490 if (sep_by_space == 2)
491 out_char (' ');
492 out_char (sign_char);
495 if (sign_posn == 0)
496 out_char (is_negative ? right_paren : ' ');
498 /* Now test whether the output width is filled. */
499 if (dest - startp < width)
501 if (left)
502 /* We simply have to fill using spaces. */
504 out_char (' ');
505 while (dest - startp < width);
506 else
508 int dist = width - (dest - startp);
509 char *cp;
510 for (cp = dest - 1; cp >= startp; --cp)
511 cp[dist] = cp[0];
513 dest += dist;
516 startp[--dist] = ' ';
517 while (dist > 0);
522 /* Terminate the string. */
523 out_char ('\0');
525 va_end (ap);
527 return dest - s - 1;