Update. Old logs are in ChangeLog.7.
[glibc.git] / stdlib / strfmon.c
blobe1ff401e721ec4373e0951792a5ccefe26a3d0e8
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)
299 if (*_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS) == CHAR_MAX)
300 right_prec = 2;
301 else
302 right_prec = *_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS);
303 break;
304 case 'n': /* Use national currency symbol. */
305 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
306 if (right_prec == -1)
307 if (*_NL_CURRENT (LC_MONETARY, FRAC_DIGITS) == CHAR_MAX)
308 right_prec = 2;
309 else
310 right_prec = *_NL_CURRENT (LC_MONETARY, FRAC_DIGITS);
311 break;
312 default: /* Any unrecognized format is an error. */
313 __set_errno (EINVAL);
314 va_end (ap);
315 return -1;
318 /* If we have to print the digits grouped determine how many
319 extra characters this means. */
320 if (group && left_prec != -1)
321 left_prec += __guess_grouping (left_prec,
322 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
323 *_NL_CURRENT (LC_MONETARY,
324 MON_THOUSANDS_SEP));
326 /* Now it's time to get the value. */
327 if (is_long_double == 1)
329 fpnum.ldbl = va_arg (ap, long double);
330 is_negative = fpnum.ldbl < 0;
331 if (is_negative)
332 fpnum.ldbl = -fpnum.ldbl;
334 else
336 fpnum.dbl = va_arg (ap, double);
337 is_negative = fpnum.dbl < 0;
338 if (is_negative)
339 fpnum.dbl = -fpnum.dbl;
342 /* We now know the sign of the value and can determine the format. */
343 if (is_negative)
345 sign_char = *_NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
346 /* If the locale does not specify a character for the
347 negative sign we use a '-'. */
348 if (sign_char == '\0')
349 sign_char = '-';
350 cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
351 sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
352 sign_posn = n_sign_posn;
354 else
356 sign_char = *_NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
357 /* If the locale does not specify a character for the
358 positive sign we use a <SP>. */
359 if (sign_char == '\0')
360 sign_char = ' ';
361 cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
362 sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
363 sign_posn = p_sign_posn;
366 /* Set default values for unspecified information. */
367 if (cs_precedes != 0)
368 cs_precedes = 1;
369 if (sep_by_space == 127)
370 sep_by_space = 0;
371 if (left_prec == -1)
372 left_prec = 0;
375 /* Perhaps we'll someday make these things configurable so
376 better start using symbolic names now. */
377 #define left_paren '('
378 #define right_paren ')'
380 startp = dest; /* Remember start so we can compute length. */
382 if (sign_posn == 0)
383 out_char (is_negative ? left_paren : ' ');
385 if (cs_precedes)
387 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
388 && sign_posn != 5)
390 out_char (sign_char);
391 if (sep_by_space == 2)
392 out_char (' ');
395 if (print_curr_symbol)
397 out_string (currency_symbol);
399 if (sign_posn == 4)
401 if (sep_by_space == 2)
402 out_char (' ');
403 out_char (sign_char);
405 else
406 if (sep_by_space == 1)
407 out_char (' ');
410 else
411 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
412 && sign_posn != 4 && sign_posn != 5)
413 out_char (sign_char);
415 /* Print the number. */
416 #ifdef USE_IN_LIBIO
417 _IO_init ((_IO_FILE *) &f, 0);
418 _IO_JUMPS ((_IO_FILE *) &f) = &_IO_str_jumps;
419 _IO_str_init_static ((_IO_FILE *) &f, dest, (s + maxsize) - dest, dest);
420 #else
421 memset((void *) &f, 0, sizeof(f));
422 f.__magic = _IOMAGIC;
423 f.__mode.__write = 1;
424 /* The buffer size is one less than MAXLEN
425 so we have space for the null terminator. */
426 f.__bufp = f.__buffer = (char *) dest;
427 f.__bufsize = (s + maxsize) - dest;
428 f.__put_limit = f.__buffer + f.__bufsize;
429 f.__get_limit = f.__buffer;
430 /* After the buffer is full (MAXLEN characters have been written),
431 any more characters written will go to the bit bucket. */
432 f.__room_funcs = __default_room_functions;
433 f.__io_funcs.__write = NULL;
434 f.__seen = 1;
435 #endif
436 /* We clear the last available byte so we can find out whether
437 the numeric representation is too long. */
438 s[maxsize - 1] = '\0';
440 info.prec = right_prec;
441 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
442 info.spec = 'f';
443 info.is_long_double = is_long_double;
444 info.is_short = 0;
445 info.is_long = 0;
446 info.alt = 0;
447 info.space = 0;
448 info.left = left;
449 info.showsign = 0;
450 info.group = group;
451 info.pad = pad;
452 info.extra = 1; /* This means use values from LC_MONETARY. */
454 ptr = &fpnum;
455 done = __printf_fp ((FILE *) &f, &info, &ptr);
456 if (done < 0)
458 va_end (ap);
459 return -1;
462 if (s[maxsize - 1] != '\0')
463 return -1;
465 dest += done;
467 if (!cs_precedes)
469 if (sign_posn == 3)
471 if (sep_by_space == 1)
472 out_char (' ');
473 out_char (sign_char);
476 if (print_curr_symbol)
478 if (sign_posn == 3 && sep_by_space == 2)
479 out_char (' ');
480 out_string (currency_symbol);
483 else
484 if (sign_posn == 2)
486 if (sep_by_space == 2)
487 out_char (' ');
488 out_char (sign_char);
491 if (sign_posn == 0)
492 out_char (is_negative ? right_paren : ' ');
494 /* Now test whether the output width is filled. */
495 if (dest - startp < width)
496 if (left)
497 /* We simply have to fill using spaces. */
499 out_char (' ');
500 while (dest - startp < width);
501 else
503 int dist = width - (dest - startp);
504 char *cp;
505 for (cp = dest - 1; cp >= startp; --cp)
506 cp[dist] = cp[0];
508 dest += dist;
511 startp[--dist] = ' ';
512 while (dist > 0);
516 /* Terminate the string. */
517 out_char ('\0');
519 va_end (ap);
521 return dest - s - 1;