update from main archive 961201
[glibc.git] / stdlib / strfmon.c
blob85f8898136f7f46d924437efcd5fdaa228cefa03
1 /* strfmon -- formating a monetary value according to the current locale
2 Copyright (C) 1996 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
19 not, 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 continue;
155 case '^': /* Don't group digits. */
156 group = 0;
157 continue;
158 case '+': /* Use +/- for sign of number. */
159 if (n_sign_posn != -1)
161 __set_errno (EINVAL);
162 va_end (ap);
163 return -1;
165 if (*_NL_CURRENT (LC_MONETARY, P_SIGN_POSN) == '\0')
166 p_sign_posn = 1;
167 else
168 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
169 if (*_NL_CURRENT (LC_MONETARY, N_SIGN_POSN) == '\0')
170 n_sign_posn = 1;
171 else
172 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
173 continue;
174 case '(': /* Use ( ) for negative sign. */
175 if (n_sign_posn != -1)
177 __set_errno (EINVAL);
178 va_end (ap);
179 return -1;
181 n_sign_posn = 5; /* This is a else unused value. */
182 continue;
183 case '!': /* Don't print the currency symbol. */
184 print_curr_symbol = 0;
185 continue;
186 case '-': /* Print left justified. */
187 left = 1;
188 continue;
189 default:
190 /* Will stop the loop. */;
192 break;
195 if (isdigit (*fmt))
197 /* Parse field width. */
198 width = to_digit (*fmt);
200 while (isdigit (*++fmt))
202 width *= 10;
203 width += to_digit (*fmt);
206 /* If we don't have enough room for the demanded width we
207 can stop now and return an error. */
208 if (dest + width >= s + maxsize)
210 __set_errno (E2BIG);
211 va_end (ap);
212 return -1;
216 /* Recognize left precision. */
217 if (*fmt == '#')
219 if (!isdigit (*++fmt))
221 __set_errno (EINVAL);
222 va_end (ap);
223 return -1;
225 left_prec = to_digit (*fmt);
227 while (isdigit (*++fmt))
229 left_prec *= 10;
230 left_prec += to_digit (*fmt);
234 /* Recognize right precision. */
235 if (*fmt == '.')
237 if (!isdigit (*++fmt))
239 __set_errno (EINVAL);
240 va_end (ap);
241 return -1;
243 right_prec = to_digit (*fmt);
245 while (isdigit (*++fmt))
247 right_prec *= 10;
248 right_prec += to_digit (*fmt);
252 /* Handle modifier. This is an extension. */
253 if (*fmt == 'L')
255 ++fmt;
256 is_long_double = 1;
259 /* Handle format specifier. */
260 switch (*fmt++)
262 case 'i': /* Use international currency symbol. */
263 currency_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
264 if (right_prec == -1)
265 if (*_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS) == '\177')
266 right_prec = 2;
267 else
268 right_prec = *_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS);
269 break;
270 case 'n': /* Use national currency symbol. */
271 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
272 if (right_prec == -1)
273 if (*_NL_CURRENT (LC_MONETARY, FRAC_DIGITS) == '\177')
274 right_prec = 2;
275 else
276 right_prec = *_NL_CURRENT (LC_MONETARY, FRAC_DIGITS);
277 break;
278 default: /* Any unrecognized format is an error. */
279 __set_errno (EINVAL);
280 va_end (ap);
281 return -1;
284 /* If we have to print the digits grouped determine how many
285 extra characters this means. */
286 if (group && left_prec != -1)
287 left_prec += __guess_grouping (left_prec,
288 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
289 *_NL_CURRENT (LC_MONETARY,
290 MON_THOUSANDS_SEP));
292 /* Now it's time to get the value. */
293 if (is_long_double == 1)
295 fpnum.ldbl = va_arg (ap, long double);
296 is_negative = fpnum.ldbl < 0;
297 if (is_negative)
298 fpnum.ldbl = -fpnum.ldbl;
300 else
302 fpnum.dbl = va_arg (ap, double);
303 is_negative = fpnum.dbl < 0;
304 if (is_negative)
305 fpnum.dbl = -fpnum.dbl;
308 /* We now know the sign of the value and can determine the format. */
309 if (is_negative)
311 sign_char = *_NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
312 /* If the locale does not specify a character for the
313 negative sign we use a '-'. */
314 if (sign_char == '\0')
315 sign_char = '-';
316 cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
317 sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
318 /* If the '(' flag is not given use the sign position from
319 the current locale. */
320 if (n_sign_posn == -1)
321 sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
322 else
323 /* This means use parentheses. */
324 sign_posn = 0;
326 else
328 sign_char = *_NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
329 /* If the locale does not specify a character for the
330 positive sign we use a <SP>. */
331 if (sign_char == '\0')
332 sign_char = ' ';
333 cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
334 sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
335 if (n_sign_posn == -1)
336 sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
337 else
338 /* Here we don't set SIGN_POSN to 0 because we don'want to
339 print <SP> instead of the braces and this is what the
340 value 5 means. */
341 sign_posn = 5;
344 /* Set default values for unspecified information. */
345 if (cs_precedes != 0)
346 cs_precedes = 1;
347 if (sep_by_space == 127)
348 sep_by_space = 0;
349 if (left_prec == -1)
350 left_prec = 0;
353 /* Perhaps we'll someday make these things configurable so
354 better start using symbolic names now. */
355 #define left_paren '('
356 #define right_paren ')'
358 startp = dest; /* Remember start so we can compute lenght. */
360 if (sign_posn == 0)
361 out_char (left_paren);
362 if (sign_posn == 5) /* This is for positive number and ( flag. */
363 out_char (' ');
365 if (cs_precedes)
367 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
368 && sign_posn != 5)
370 out_char (sign_char);
371 if (sep_by_space == 2)
372 out_char (' ');
375 if (print_curr_symbol)
377 out_string (currency_symbol);
379 if (sign_posn == 4)
381 if (sep_by_space == 2)
382 out_char (' ');
383 out_char (sign_char);
385 else
386 if (sep_by_space == 1)
387 out_char (' ');
390 else
391 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
392 && sign_posn != 4 && sign_posn != 5)
393 out_char (sign_char);
395 /* Print the number. */
396 #ifdef USE_IN_LIBIO
397 _IO_init ((_IO_FILE *) &f, 0);
398 _IO_JUMPS ((_IO_FILE *) &f) = &_IO_str_jumps;
399 _IO_str_init_static ((_IO_FILE *) &f, dest, (s + maxsize) - dest, dest);
400 #else
401 memset((void *) &f, 0, sizeof(f));
402 f.__magic = _IOMAGIC;
403 f.__mode.__write = 1;
404 /* The buffer size is one less than MAXLEN
405 so we have space for the null terminator. */
406 f.__bufp = f.__buffer = (char *) dest;
407 f.__bufsize = (s + maxsize) - dest;
408 f.__put_limit = f.__buffer + f.__bufsize;
409 f.__get_limit = f.__buffer;
410 /* After the buffer is full (MAXLEN characters have been written),
411 any more characters written will go to the bit bucket. */
412 f.__room_funcs = __default_room_functions;
413 f.__io_funcs.__write = NULL;
414 f.__seen = 1;
415 #endif
416 /* We clear the last available byte so we can find out whether
417 the numeric representation is too long. */
418 s[maxsize - 1] = '\0';
420 info.prec = right_prec;
421 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
422 info.spec = 'f';
423 info.is_long_double = is_long_double;
424 info.is_short = 0;
425 info.is_long = 0;
426 info.alt = 0;
427 info.space = 0;
428 info.left = left;
429 info.showsign = 0;
430 info.group = group;
431 info.pad = pad;
432 info.extra = 1; /* This means use values from LC_MONETARY. */
434 ptr = &fpnum;
435 done = __printf_fp ((FILE *) &f, &info, &ptr);
436 if (done < 0)
438 va_end (ap);
439 return -1;
442 if (s[maxsize - 1] != '\0')
443 return -1;
445 dest += done;
447 if (!cs_precedes)
449 if (sign_posn == 3)
451 if (sep_by_space == 1)
452 out_char (' ');
453 out_char (sign_char);
456 if (print_curr_symbol)
458 if (sign_posn == 3 && sep_by_space == 2)
459 out_char (' ');
460 out_string (currency_symbol);
463 else
464 if (sign_posn == 2)
466 if (sep_by_space == 2)
467 out_char (' ');
468 out_char (sign_char);
471 if (sign_posn == 0)
472 out_char (right_paren);
473 if (sign_posn == 5)
474 out_char (' '); /* This is for positive number and ( flag. */
476 /* Now test whether the output width is filled. */
477 if (dest - startp < width)
478 if (left)
479 /* We simply have to fill using spaces. */
481 out_char (' ');
482 while (dest - startp < width);
483 else
485 int dist = width - (dest - startp);
486 char *cp;
487 for (cp = dest - 1; cp >= startp; --cp)
488 cp[dist] = cp[0];
490 dest += dist;
493 startp[--dist] = ' ';
494 while (dist > 0);
498 /* Terminate the string. */
499 out_char ('\0');
501 va_end (ap);
503 return dest - s - 1;