Update.
[glibc.git] / stdlib / strfmon.c
blobc3dac412a47d37c51edbd04d38b2137eb9f6878f
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')
57 extern int __printf_fp (FILE *, const struct printf_info *,
58 const void **const);
59 /* This function determines the number of digit groups in the output.
60 The definition is in printf_fp.c. */
61 extern unsigned int __guess_grouping (unsigned int intdig_max,
62 const char *grouping, wchar_t sepchar);
65 /* We have to overcome some problems with this implementation. On the
66 one hand the strfmon() function is specified by in XPG4 and of
67 course it has to follow this. But on the other hand POSIX.2
68 specifies some information in the LC_MONETARY category which should
69 be used, too. Some of the information contradicts the information
70 which can be specified in format string. */
71 ssize_t
72 strfmon (char *s, size_t maxsize, const char *format, ...)
74 #ifdef USE_IN_LIBIO
75 _IO_strfile f;
76 #else
77 FILE f;
78 #endif
79 struct printf_info info;
80 va_list ap; /* Scan through the varargs. */
81 char *dest; /* Pointer so copy the output. */
82 const char *fmt; /* Pointer that walks through format. */
84 va_start (ap, format);
86 dest = s;
87 fmt = format;
89 /* Loop through the format-string. */
90 while (*fmt != '\0')
92 /* The floating-point value to output. */
93 union
95 double dbl;
96 __long_double_t ldbl;
98 fpnum;
99 int print_curr_symbol;
100 int left_prec;
101 int right_prec;
102 int group;
103 char pad;
104 int is_long_double;
105 int p_sign_posn;
106 int n_sign_posn;
107 int sign_posn;
108 int left;
109 int is_negative;
110 int sep_by_space;
111 int cs_precedes;
112 char sign_char;
113 int done;
114 const char *currency_symbol;
115 int width;
116 char *startp;
117 const void *ptr;
119 /* Process all character which do not introduce a format
120 specification. */
121 if (*fmt != '%')
123 out_char (*fmt++);
124 continue;
127 /* "%%" means a single '%' character. */
128 if (fmt[1] == '%')
130 out_char (*++fmt);
131 ++fmt;
132 continue;
135 /* Defaults for formatting. */
136 print_curr_symbol = 1; /* Print the currency symbol. */
137 left_prec = -1; /* No left precision specified. */
138 right_prec = -1; /* No right precision specified. */
139 group = 1; /* Print digits grouped. */
140 pad = ' '; /* Fill character is <SP>. */
141 is_long_double = 0; /* Double argument by default. */
142 p_sign_posn = -1; /* This indicates whether the */
143 n_sign_posn = -1; /* '(' flag is given. */
144 width = -1; /* No width specified so far. */
145 left = 0; /* Right justified by default. */
147 /* Parse group characters. */
148 while (1)
150 switch (*++fmt)
152 case '=': /* Set fill character. */
153 pad = *++fmt;
154 if (pad == '\0')
156 /* Premature EOS. */
157 __set_errno (EINVAL);
158 va_end (ap);
159 return -1;
161 continue;
162 case '^': /* Don't group digits. */
163 group = 0;
164 continue;
165 case '+': /* Use +/- for sign of number. */
166 if (n_sign_posn != -1)
168 __set_errno (EINVAL);
169 va_end (ap);
170 return -1;
172 if (*_NL_CURRENT (LC_MONETARY, P_SIGN_POSN) == '\0')
173 p_sign_posn = 1;
174 else
175 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
176 if (*_NL_CURRENT (LC_MONETARY, N_SIGN_POSN) == '\0')
177 n_sign_posn = 1;
178 else
179 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
180 continue;
181 case '(': /* Use ( ) for negative sign. */
182 if (n_sign_posn != -1)
184 __set_errno (EINVAL);
185 va_end (ap);
186 return -1;
188 p_sign_posn = 0;
189 n_sign_posn = 0;
190 continue;
191 case '!': /* Don't print the currency symbol. */
192 print_curr_symbol = 0;
193 continue;
194 case '-': /* Print left justified. */
195 left = 1;
196 continue;
197 default:
198 /* Will stop the loop. */;
200 break;
203 /* If not specified by the format string now find the values for
204 the format specification. */
205 if (p_sign_posn == -1)
206 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
207 if (n_sign_posn == -1)
208 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
210 if (isdigit (*fmt))
212 /* Parse field width. */
213 width = to_digit (*fmt);
215 while (isdigit (*++fmt))
217 width *= 10;
218 width += to_digit (*fmt);
221 /* If we don't have enough room for the demanded width we
222 can stop now and return an error. */
223 if (dest + width >= s + maxsize)
225 __set_errno (E2BIG);
226 va_end (ap);
227 return -1;
231 /* Recognize left precision. */
232 if (*fmt == '#')
234 if (!isdigit (*++fmt))
236 __set_errno (EINVAL);
237 va_end (ap);
238 return -1;
240 left_prec = to_digit (*fmt);
242 while (isdigit (*++fmt))
244 left_prec *= 10;
245 left_prec += to_digit (*fmt);
249 /* Recognize right precision. */
250 if (*fmt == '.')
252 if (!isdigit (*++fmt))
254 __set_errno (EINVAL);
255 va_end (ap);
256 return -1;
258 right_prec = to_digit (*fmt);
260 while (isdigit (*++fmt))
262 right_prec *= 10;
263 right_prec += to_digit (*fmt);
267 /* Handle modifier. This is an extension. */
268 if (*fmt == 'L')
270 ++fmt;
271 is_long_double = 1;
274 /* Handle format specifier. */
275 switch (*fmt++)
277 case 'i': /* Use international currency symbol. */
278 currency_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
279 if (right_prec == -1)
280 if (*_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS) == '\177')
281 right_prec = 2;
282 else
283 right_prec = *_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS);
284 break;
285 case 'n': /* Use national currency symbol. */
286 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
287 if (right_prec == -1)
288 if (*_NL_CURRENT (LC_MONETARY, FRAC_DIGITS) == '\177')
289 right_prec = 2;
290 else
291 right_prec = *_NL_CURRENT (LC_MONETARY, FRAC_DIGITS);
292 break;
293 default: /* Any unrecognized format is an error. */
294 __set_errno (EINVAL);
295 va_end (ap);
296 return -1;
299 /* If we have to print the digits grouped determine how many
300 extra characters this means. */
301 if (group && left_prec != -1)
302 left_prec += __guess_grouping (left_prec,
303 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
304 *_NL_CURRENT (LC_MONETARY,
305 MON_THOUSANDS_SEP));
307 /* Now it's time to get the value. */
308 if (is_long_double == 1)
310 fpnum.ldbl = va_arg (ap, long double);
311 is_negative = fpnum.ldbl < 0;
312 if (is_negative)
313 fpnum.ldbl = -fpnum.ldbl;
315 else
317 fpnum.dbl = va_arg (ap, double);
318 is_negative = fpnum.dbl < 0;
319 if (is_negative)
320 fpnum.dbl = -fpnum.dbl;
323 /* We now know the sign of the value and can determine the format. */
324 if (is_negative)
326 sign_char = *_NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
327 /* If the locale does not specify a character for the
328 negative sign we use a '-'. */
329 if (sign_char == '\0')
330 sign_char = '-';
331 cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
332 sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
333 sign_posn = n_sign_posn;
335 else
337 sign_char = *_NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
338 /* If the locale does not specify a character for the
339 positive sign we use a <SP>. */
340 if (sign_char == '\0')
341 sign_char = ' ';
342 cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
343 sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
344 sign_posn = p_sign_posn;
347 /* Set default values for unspecified information. */
348 if (cs_precedes != 0)
349 cs_precedes = 1;
350 if (sep_by_space == 127)
351 sep_by_space = 0;
352 if (left_prec == -1)
353 left_prec = 0;
356 /* Perhaps we'll someday make these things configurable so
357 better start using symbolic names now. */
358 #define left_paren '('
359 #define right_paren ')'
361 startp = dest; /* Remember start so we can compute length. */
363 if (sign_posn == 0)
364 out_char (is_negative ? left_paren : ' ');
366 if (cs_precedes)
368 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
369 && sign_posn != 5)
371 out_char (sign_char);
372 if (sep_by_space == 2)
373 out_char (' ');
376 if (print_curr_symbol)
378 out_string (currency_symbol);
380 if (sign_posn == 4)
382 if (sep_by_space == 2)
383 out_char (' ');
384 out_char (sign_char);
386 else
387 if (sep_by_space == 1)
388 out_char (' ');
391 else
392 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
393 && sign_posn != 4 && sign_posn != 5)
394 out_char (sign_char);
396 /* Print the number. */
397 #ifdef USE_IN_LIBIO
398 _IO_init ((_IO_FILE *) &f, 0);
399 _IO_JUMPS ((_IO_FILE *) &f) = &_IO_str_jumps;
400 _IO_str_init_static ((_IO_FILE *) &f, dest, (s + maxsize) - dest, dest);
401 #else
402 memset((void *) &f, 0, sizeof(f));
403 f.__magic = _IOMAGIC;
404 f.__mode.__write = 1;
405 /* The buffer size is one less than MAXLEN
406 so we have space for the null terminator. */
407 f.__bufp = f.__buffer = (char *) dest;
408 f.__bufsize = (s + maxsize) - dest;
409 f.__put_limit = f.__buffer + f.__bufsize;
410 f.__get_limit = f.__buffer;
411 /* After the buffer is full (MAXLEN characters have been written),
412 any more characters written will go to the bit bucket. */
413 f.__room_funcs = __default_room_functions;
414 f.__io_funcs.__write = NULL;
415 f.__seen = 1;
416 #endif
417 /* We clear the last available byte so we can find out whether
418 the numeric representation is too long. */
419 s[maxsize - 1] = '\0';
421 info.prec = right_prec;
422 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
423 info.spec = 'f';
424 info.is_long_double = is_long_double;
425 info.is_short = 0;
426 info.is_long = 0;
427 info.alt = 0;
428 info.space = 0;
429 info.left = left;
430 info.showsign = 0;
431 info.group = group;
432 info.pad = pad;
433 info.extra = 1; /* This means use values from LC_MONETARY. */
435 ptr = &fpnum;
436 done = __printf_fp ((FILE *) &f, &info, &ptr);
437 if (done < 0)
439 va_end (ap);
440 return -1;
443 if (s[maxsize - 1] != '\0')
444 return -1;
446 dest += done;
448 if (!cs_precedes)
450 if (sign_posn == 3)
452 if (sep_by_space == 1)
453 out_char (' ');
454 out_char (sign_char);
457 if (print_curr_symbol)
459 if (sign_posn == 3 && sep_by_space == 2)
460 out_char (' ');
461 out_string (currency_symbol);
464 else
465 if (sign_posn == 2)
467 if (sep_by_space == 2)
468 out_char (' ');
469 out_char (sign_char);
472 if (sign_posn == 0)
473 out_char (is_negative ? right_paren : ' ');
475 /* Now test whether the output width is filled. */
476 if (dest - startp < width)
477 if (left)
478 /* We simply have to fill using spaces. */
480 out_char (' ');
481 while (dest - startp < width);
482 else
484 int dist = width - (dest - startp);
485 char *cp;
486 for (cp = dest - 1; cp >= startp; --cp)
487 cp[dist] = cp[0];
489 dest += dist;
492 startp[--dist] = ' ';
493 while (dist > 0);
497 /* Terminate the string. */
498 out_char ('\0');
500 va_end (ap);
502 return dest - s - 1;