]> git.ipfire.org Git - thirdparty/glibc.git/blob - stdlib/strfmon.c
Update.
[thirdparty/glibc.git] / stdlib / strfmon.c
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.
6
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.
11
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.
16
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. */
21
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"
35
36
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)
47
48 #define out_string(String) \
49 do { \
50 const char *_s = (String); \
51 while (*_s) \
52 out_char (*_s++); \
53 } while (0)
54
55 #define to_digit(Ch) ((Ch) - '0')
56
57
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
67
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);
74
75
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
89 {
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. */
102
103 va_start (ap, format);
104
105 dest = s;
106 fmt = format;
107
108 /* Loop through the format-string. */
109 while (*fmt != '\0')
110 {
111 /* The floating-point value to output. */
112 union
113 {
114 double dbl;
115 __long_double_t ldbl;
116 }
117 fpnum;
118 int print_curr_symbol;
119 int left_prec;
120 int left_pad;
121 int right_prec;
122 int group;
123 char pad;
124 int is_long_double;
125 int p_sign_posn;
126 int n_sign_posn;
127 int sign_posn;
128 int other_sign_posn;
129 int left;
130 int is_negative;
131 int sep_by_space;
132 int other_sep_by_space;
133 int cs_precedes;
134 int other_cs_precedes;
135 const char *sign_string;
136 const char *other_sign_string;
137 int done;
138 const char *currency_symbol;
139 int width;
140 char *startp;
141 const void *ptr;
142
143 /* Process all character which do not introduce a format
144 specification. */
145 if (*fmt != '%')
146 {
147 out_char (*fmt++);
148 continue;
149 }
150
151 /* "%%" means a single '%' character. */
152 if (fmt[1] == '%')
153 {
154 out_char (*++fmt);
155 ++fmt;
156 continue;
157 }
158
159 /* Defaults for formatting. */
160 print_curr_symbol = 1; /* Print the currency symbol. */
161 left_prec = -1; /* No left precision specified. */
162 right_prec = -1; /* No right precision specified. */
163 group = 1; /* Print digits grouped. */
164 pad = ' '; /* Fill character is <SP>. */
165 is_long_double = 0; /* Double argument by default. */
166 p_sign_posn = -1; /* This indicates whether the */
167 n_sign_posn = -1; /* '(' flag is given. */
168 width = -1; /* No width specified so far. */
169 left = 0; /* Right justified by default. */
170
171 /* Parse group characters. */
172 while (1)
173 {
174 switch (*++fmt)
175 {
176 case '=': /* Set fill character. */
177 pad = *++fmt;
178 if (pad == '\0')
179 {
180 /* Premature EOS. */
181 __set_errno (EINVAL);
182 va_end (ap);
183 return -1;
184 }
185 continue;
186 case '^': /* Don't group digits. */
187 group = 0;
188 continue;
189 case '+': /* Use +/- for sign of number. */
190 if (n_sign_posn != -1)
191 {
192 __set_errno (EINVAL);
193 va_end (ap);
194 return -1;
195 }
196 if (*_NL_CURRENT (LC_MONETARY, P_SIGN_POSN) == '\0')
197 p_sign_posn = 1;
198 else
199 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
200 if (*_NL_CURRENT (LC_MONETARY, N_SIGN_POSN) == '\0')
201 n_sign_posn = 1;
202 else
203 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
204 continue;
205 case '(': /* Use ( ) for negative sign. */
206 if (n_sign_posn != -1)
207 {
208 __set_errno (EINVAL);
209 va_end (ap);
210 return -1;
211 }
212 p_sign_posn = 0;
213 n_sign_posn = 0;
214 continue;
215 case '!': /* Don't print the currency symbol. */
216 print_curr_symbol = 0;
217 continue;
218 case '-': /* Print left justified. */
219 left = 1;
220 continue;
221 default:
222 /* Will stop the loop. */;
223 }
224 break;
225 }
226
227 /* If not specified by the format string now find the values for
228 the format specification. */
229 if (p_sign_posn == -1)
230 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
231 if (n_sign_posn == -1)
232 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
233
234 if (isdigit (*fmt))
235 {
236 /* Parse field width. */
237 width = to_digit (*fmt);
238
239 while (isdigit (*++fmt))
240 {
241 width *= 10;
242 width += to_digit (*fmt);
243 }
244
245 /* If we don't have enough room for the demanded width we
246 can stop now and return an error. */
247 if (dest + width >= s + maxsize)
248 {
249 __set_errno (E2BIG);
250 va_end (ap);
251 return -1;
252 }
253 }
254
255 /* Recognize left precision. */
256 if (*fmt == '#')
257 {
258 if (!isdigit (*++fmt))
259 {
260 __set_errno (EINVAL);
261 va_end (ap);
262 return -1;
263 }
264 left_prec = to_digit (*fmt);
265
266 while (isdigit (*++fmt))
267 {
268 left_prec *= 10;
269 left_prec += to_digit (*fmt);
270 }
271 }
272
273 /* Recognize right precision. */
274 if (*fmt == '.')
275 {
276 if (!isdigit (*++fmt))
277 {
278 __set_errno (EINVAL);
279 va_end (ap);
280 return -1;
281 }
282 right_prec = to_digit (*fmt);
283
284 while (isdigit (*++fmt))
285 {
286 right_prec *= 10;
287 right_prec += to_digit (*fmt);
288 }
289 }
290
291 /* Handle modifier. This is an extension. */
292 if (*fmt == 'L')
293 {
294 ++fmt;
295 is_long_double = 1;
296 }
297
298 /* Handle format specifier. */
299 switch (*fmt++)
300 {
301 case 'i': /* Use international currency symbol. */
302 currency_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
303 if (right_prec == -1)
304 {
305 if (*_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS) == CHAR_MAX)
306 right_prec = 2;
307 else
308 right_prec = *_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS);
309 }
310 break;
311 case 'n': /* Use national currency symbol. */
312 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
313 if (right_prec == -1)
314 {
315 if (*_NL_CURRENT (LC_MONETARY, FRAC_DIGITS) == CHAR_MAX)
316 right_prec = 2;
317 else
318 right_prec = *_NL_CURRENT (LC_MONETARY, FRAC_DIGITS);
319 }
320 break;
321 default: /* Any unrecognized format is an error. */
322 __set_errno (EINVAL);
323 va_end (ap);
324 return -1;
325 }
326
327 /* If we have to print the digits grouped determine how many
328 extra characters this means. */
329 if (group && left_prec != -1)
330 left_prec += __guess_grouping (left_prec,
331 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
332 *_NL_CURRENT (LC_MONETARY,
333 MON_THOUSANDS_SEP));
334
335 /* Now it's time to get the value. */
336 if (is_long_double == 1)
337 {
338 fpnum.ldbl = va_arg (ap, long double);
339 is_negative = fpnum.ldbl < 0;
340 if (is_negative)
341 fpnum.ldbl = -fpnum.ldbl;
342 }
343 else
344 {
345 fpnum.dbl = va_arg (ap, double);
346 is_negative = fpnum.dbl < 0;
347 if (is_negative)
348 fpnum.dbl = -fpnum.dbl;
349 }
350
351 /* We now know the sign of the value and can determine the format. */
352 if (is_negative)
353 {
354 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
355 /* If the locale does not specify a character for the
356 negative sign we use a '-'. */
357 if (*sign_string == '\0')
358 sign_string = (const char *) "-";
359 cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
360 sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
361 sign_posn = n_sign_posn;
362
363 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
364 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
365 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
366 other_sign_posn = p_sign_posn;
367 }
368 else
369 {
370 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
371 cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
372 sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
373 sign_posn = p_sign_posn;
374
375 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
376 if (*other_sign_string == '\0')
377 other_sign_string = (const char *) "-";
378 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
379 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
380 other_sign_posn = n_sign_posn;
381 }
382
383 /* Set default values for unspecified information. */
384 if (cs_precedes != 0)
385 cs_precedes = 1;
386 if (other_cs_precedes != 0)
387 other_cs_precedes = 1;
388 if (sep_by_space == 127)
389 sep_by_space = 0;
390 if (other_sep_by_space == 127)
391 other_sep_by_space = 0;
392
393 /* Set the left precision and padding needed for alignment */
394 if (left_prec == -1)
395 {
396 left_prec = 0;
397 left_pad = 0;
398 }
399 else
400 {
401 /* Set left_pad to number of spaces needed to align positive
402 and negative formats */
403
404 int sign_precedes = 0;
405 int other_sign_precedes = 0;
406
407 left_pad = 0;
408
409 if (!cs_precedes && other_cs_precedes)
410 {
411 /* The other format has currency symbol preceding value,
412 but this format doesn't, so pad by the relevant amount */
413 left_pad += strlen (currency_symbol);
414 if (other_sep_by_space != 0)
415 ++left_pad;
416 }
417
418 /* Work out for each format whether a sign (or left parenthesis)
419 precedes the value */
420 if (sign_posn == 0 || sign_posn == 1)
421 sign_precedes = 1;
422 if (other_sign_posn == 0 || other_sign_posn == 1)
423 other_sign_precedes = 1;
424 if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
425 sign_precedes = 1;
426 if (other_cs_precedes
427 && (other_sign_posn == 3 || other_sign_posn == 4))
428 other_sign_precedes = 1;
429
430 if (!sign_precedes && other_sign_precedes)
431 {
432 /* The other format has a sign (or left parenthesis) preceding
433 the value, but this format doesn't */
434 if (other_sign_posn == 0)
435 ++left_pad;
436 else
437 left_pad += strlen (other_sign_string);
438 }
439 else if (sign_precedes && other_sign_precedes)
440 {
441 /* Both formats have a sign (or left parenthesis) preceding
442 the value, so compare their lengths */
443 int len_diff =
444 ((other_sign_posn == 0 ? 1 : (int) strlen (other_sign_string))
445 - (sign_posn == 0 ? 1 : (int) strlen (sign_string)));
446 if (len_diff > 0)
447 left_pad += len_diff;
448 }
449 }
450
451 /* Perhaps we'll someday make these things configurable so
452 better start using symbolic names now. */
453 #define left_paren '('
454 #define right_paren ')'
455
456 startp = dest; /* Remember start so we can compute length. */
457
458 while (left_pad-- > 0)
459 out_char (' ');
460
461 if (sign_posn == 0 && is_negative)
462 out_char (left_paren);
463
464 if (cs_precedes)
465 {
466 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
467 && sign_posn != 5)
468 {
469 out_string (sign_string);
470 if (sep_by_space == 2)
471 out_char (' ');
472 }
473
474 if (print_curr_symbol)
475 {
476 out_string (currency_symbol);
477
478 if (sign_posn == 4)
479 {
480 if (sep_by_space == 2)
481 out_char (' ');
482 out_string (sign_string);
483 }
484 else
485 if (sep_by_space == 1)
486 out_char (' ');
487 }
488 }
489 else
490 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
491 && sign_posn != 4 && sign_posn != 5)
492 out_string (sign_string);
493
494 /* Print the number. */
495 #ifdef USE_IN_LIBIO
496 _IO_init ((_IO_FILE *) &f, 0);
497 _IO_JUMPS ((_IO_FILE *) &f) = &_IO_str_jumps;
498 _IO_str_init_static ((_IO_FILE *) &f, dest, (s + maxsize) - dest, dest);
499 #else
500 memset ((void *) &f, 0, sizeof (f));
501 f.__magic = _IOMAGIC;
502 f.__mode.__write = 1;
503 /* The buffer size is one less than MAXLEN
504 so we have space for the null terminator. */
505 f.__bufp = f.__buffer = (char *) dest;
506 f.__bufsize = (s + maxsize) - dest;
507 f.__put_limit = f.__buffer + f.__bufsize;
508 f.__get_limit = f.__buffer;
509 /* After the buffer is full (MAXLEN characters have been written),
510 any more characters written will go to the bit bucket. */
511 f.__room_funcs = __default_room_functions;
512 f.__io_funcs.__write = NULL;
513 f.__seen = 1;
514 #endif
515 /* We clear the last available byte so we can find out whether
516 the numeric representation is too long. */
517 s[maxsize - 1] = '\0';
518
519 info.prec = right_prec;
520 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
521 info.spec = 'f';
522 info.is_long_double = is_long_double;
523 info.is_short = 0;
524 info.is_long = 0;
525 info.alt = 0;
526 info.space = 0;
527 info.left = left;
528 info.showsign = 0;
529 info.group = group;
530 info.pad = pad;
531 info.extra = 1; /* This means use values from LC_MONETARY. */
532 info.wide = 0;
533
534 ptr = &fpnum;
535 done = __printf_fp ((FILE *) &f, &info, &ptr);
536 if (done < 0)
537 {
538 va_end (ap);
539 return -1;
540 }
541
542 if (s[maxsize - 1] != '\0')
543 {
544 __set_errno (E2BIG);
545 return -1;
546 }
547
548 dest += done;
549
550 if (!cs_precedes)
551 {
552 if (sign_posn == 3)
553 {
554 if (sep_by_space == 1)
555 out_char (' ');
556 out_string (sign_string);
557 }
558
559 if (print_curr_symbol)
560 {
561 if ((sign_posn == 3 && sep_by_space == 2)
562 || (sign_posn == 2 && sep_by_space == 1)
563 || (sign_posn == 0 && sep_by_space == 1))
564 out_char (' ');
565 out_string (currency_symbol);
566 if (sign_posn == 4)
567 {
568 if (sep_by_space == 2)
569 out_char (' ');
570 out_string (sign_string);
571 }
572 }
573 }
574
575 if (sign_posn == 2)
576 {
577 if (sep_by_space == 2)
578 out_char (' ');
579 out_string (sign_string);
580 }
581
582 if (sign_posn == 0 && is_negative)
583 out_char (right_paren);
584
585 /* Now test whether the output width is filled. */
586 if (dest - startp < width)
587 {
588 if (left)
589 /* We simply have to fill using spaces. */
590 do
591 out_char (' ');
592 while (dest - startp < width);
593 else
594 {
595 int dist = width - (dest - startp);
596 char *cp;
597 for (cp = dest - 1; cp >= startp; --cp)
598 cp[dist] = cp[0];
599
600 dest += dist;
601
602 do
603 startp[--dist] = ' ';
604 while (dist > 0);
605 }
606 }
607 }
608
609 /* Terminate the string. */
610 *dest = '\0';
611
612 va_end (ap);
613
614 return dest - s;
615 }