2005-01-20 Ulrich Drepper <drepper@redhat.com>
[glibc/history.git] / stdlib / strfmon_l.c
blob58dab340cf112a54c039097534989ff0128dbfbd
1 /* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996, 1997, 2002, 2004 Free Software Foundation, Inc.
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
6 The GNU C Library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Lesser General Public
8 License as published by the Free Software Foundation; either
9 version 2.1 of the License, or (at your option) any later version.
11 The GNU C Library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Lesser General Public License for more details.
16 You should have received a copy of the GNU Lesser General Public
17 License along with the GNU C Library; if not, write to the Free
18 Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA. */
21 #include <ctype.h>
22 #include <errno.h>
23 #include <langinfo.h>
24 #include <locale.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 out_nstring(String, N) \
56 do { \
57 int _n = (N); \
58 const char *_s = (String); \
59 while (_n-- > 0) \
60 out_char (*_s++); \
61 } while (0)
63 #define to_digit(Ch) ((Ch) - '0')
66 /* We use this code also for the extended locale handling where the
67 function gets as an additional argument the locale which has to be
68 used. To access the values we have to redefine the _NL_CURRENT
69 macro. */
70 #undef _NL_CURRENT
71 #define _NL_CURRENT(category, item) \
72 (current->values[_NL_ITEM_INDEX (item)].string)
74 extern int __printf_fp (FILE *, const struct printf_info *,
75 const void *const *);
76 libc_hidden_proto (__printf_fp)
77 /* This function determines the number of digit groups in the output.
78 The definition is in printf_fp.c. */
79 extern unsigned int __guess_grouping (unsigned int intdig_max,
80 const char *grouping, wchar_t sepchar);
83 /* We have to overcome some problems with this implementation. On the
84 one hand the strfmon() function is specified in XPG4 and of course
85 it has to follow this. But on the other hand POSIX.2 specifies
86 some information in the LC_MONETARY category which should be used,
87 too. Some of the information contradicts the information which can
88 be specified in format string. */
89 ssize_t
90 __vstrfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format,
91 va_list ap)
93 struct locale_data *current = loc->__locales[LC_MONETARY];
94 #ifdef USE_IN_LIBIO
95 _IO_strfile f;
96 # ifdef _IO_MTSAFE_IO
97 _IO_lock_t lock;
98 # endif
99 #else
100 FILE f;
101 #endif
102 struct printf_info info;
103 char *dest; /* Pointer so copy the output. */
104 const char *fmt; /* Pointer that walks through format. */
106 dest = s;
107 fmt = format;
109 /* Loop through the format-string. */
110 while (*fmt != '\0')
112 /* The floating-point value to output. */
113 union
115 double dbl;
116 __long_double_t ldbl;
118 fpnum;
119 int int_format;
120 int print_curr_symbol;
121 int left_prec;
122 int left_pad;
123 int right_prec;
124 int group;
125 char pad;
126 int is_long_double;
127 int p_sign_posn;
128 int n_sign_posn;
129 int sign_posn;
130 int other_sign_posn;
131 int left;
132 int is_negative;
133 int sep_by_space;
134 int other_sep_by_space;
135 int cs_precedes;
136 int other_cs_precedes;
137 const char *sign_string;
138 const char *other_sign_string;
139 int done;
140 const char *currency_symbol;
141 size_t currency_symbol_len;
142 int width;
143 char *startp;
144 const void *ptr;
145 char space_char;
147 /* Process all character which do not introduce a format
148 specification. */
149 if (*fmt != '%')
151 out_char (*fmt++);
152 continue;
155 /* "%%" means a single '%' character. */
156 if (fmt[1] == '%')
158 out_char (*++fmt);
159 ++fmt;
160 continue;
163 /* Defaults for formatting. */
164 int_format = 0; /* Use international curr. symbol */
165 print_curr_symbol = 1; /* Print the currency symbol. */
166 left_prec = -1; /* No left precision specified. */
167 right_prec = -1; /* No right precision specified. */
168 group = 1; /* Print digits grouped. */
169 pad = ' '; /* Fill character is <SP>. */
170 is_long_double = 0; /* Double argument by default. */
171 p_sign_posn = -1; /* This indicates whether the */
172 n_sign_posn = -1; /* '(' flag is given. */
173 width = -1; /* No width specified so far. */
174 left = 0; /* Right justified by default. */
176 /* Parse group characters. */
177 while (1)
179 switch (*++fmt)
181 case '=': /* Set fill character. */
182 pad = *++fmt;
183 if (pad == '\0')
185 /* Premature EOS. */
186 __set_errno (EINVAL);
187 return -1;
189 continue;
190 case '^': /* Don't group digits. */
191 group = 0;
192 continue;
193 case '+': /* Use +/- for sign of number. */
194 if (n_sign_posn != -1)
196 __set_errno (EINVAL);
197 return -1;
199 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
200 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
201 continue;
202 case '(': /* Use ( ) for negative sign. */
203 if (n_sign_posn != -1)
205 __set_errno (EINVAL);
206 return -1;
208 p_sign_posn = 0;
209 n_sign_posn = 0;
210 continue;
211 case '!': /* Don't print the currency symbol. */
212 print_curr_symbol = 0;
213 continue;
214 case '-': /* Print left justified. */
215 left = 1;
216 continue;
217 default:
218 /* Will stop the loop. */;
220 break;
223 if (isdigit (*fmt))
225 /* Parse field width. */
226 width = to_digit (*fmt);
228 while (isdigit (*++fmt))
230 width *= 10;
231 width += to_digit (*fmt);
234 /* If we don't have enough room for the demanded width we
235 can stop now and return an error. */
236 if (dest + width >= s + maxsize)
238 __set_errno (E2BIG);
239 return -1;
243 /* Recognize left precision. */
244 if (*fmt == '#')
246 if (!isdigit (*++fmt))
248 __set_errno (EINVAL);
249 return -1;
251 left_prec = to_digit (*fmt);
253 while (isdigit (*++fmt))
255 left_prec *= 10;
256 left_prec += to_digit (*fmt);
260 /* Recognize right precision. */
261 if (*fmt == '.')
263 if (!isdigit (*++fmt))
265 __set_errno (EINVAL);
266 return -1;
268 right_prec = to_digit (*fmt);
270 while (isdigit (*++fmt))
272 right_prec *= 10;
273 right_prec += to_digit (*fmt);
277 /* Handle modifier. This is an extension. */
278 if (*fmt == 'L')
280 ++fmt;
281 is_long_double = 1;
284 /* Handle format specifier. */
285 char int_symbol[4];
286 switch (*fmt++)
288 case 'i': { /* Use international currency symbol. */
289 const char *int_curr_symbol;
291 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
292 strncpy(int_symbol, int_curr_symbol, 3);
293 int_symbol[3] = '\0';
295 currency_symbol_len = 3;
296 currency_symbol = &int_symbol[0];
297 space_char = int_curr_symbol[3];
298 int_format = 1;
299 break;
301 case 'n': /* Use national currency symbol. */
302 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
303 currency_symbol_len = strlen (currency_symbol);
304 space_char = ' ';
305 int_format = 0;
306 break;
307 default: /* Any unrecognized format is an error. */
308 __set_errno (EINVAL);
309 return -1;
312 /* If not specified by the format string now find the values for
313 the format specification. */
314 if (p_sign_posn == -1)
315 p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
316 if (n_sign_posn == -1)
317 n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
319 if (right_prec == -1)
321 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
323 if (right_prec == CHAR_MAX)
324 right_prec = 2;
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));
335 /* Now it's time to get the value. */
336 if (is_long_double == 1)
338 fpnum.ldbl = va_arg (ap, long double);
339 is_negative = fpnum.ldbl < 0;
340 if (is_negative)
341 fpnum.ldbl = -fpnum.ldbl;
343 else
345 fpnum.dbl = va_arg (ap, double);
346 is_negative = fpnum.dbl < 0;
347 if (is_negative)
348 fpnum.dbl = -fpnum.dbl;
351 /* We now know the sign of the value and can determine the format. */
352 if (is_negative)
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, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
360 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
361 sign_posn = n_sign_posn;
363 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
364 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
365 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
366 other_sign_posn = p_sign_posn;
368 else
370 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
371 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
372 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
373 sign_posn = p_sign_posn;
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, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
379 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
380 other_sign_posn = n_sign_posn;
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 == CHAR_MAX)
389 sep_by_space = 0;
390 if (other_sep_by_space == CHAR_MAX)
391 other_sep_by_space = 0;
392 if (sign_posn == CHAR_MAX)
393 sign_posn = 1;
394 if (other_sign_posn == CHAR_MAX)
395 other_sign_posn = 1;
397 /* Check for degenerate cases */
398 if (sep_by_space == 2)
400 if (sign_posn == 0 ||
401 (sign_posn == 1 && !cs_precedes) ||
402 (sign_posn == 2 && cs_precedes))
403 /* sign and symbol are not adjacent, so no separator */
404 sep_by_space = 0;
406 if (other_sep_by_space == 2)
408 if (other_sign_posn == 0 ||
409 (other_sign_posn == 1 && !other_cs_precedes) ||
410 (other_sign_posn == 2 && other_cs_precedes))
411 /* sign and symbol are not adjacent, so no separator */
412 other_sep_by_space = 0;
415 /* Set the left precision and padding needed for alignment */
416 if (left_prec == -1)
418 left_prec = 0;
419 left_pad = 0;
421 else
423 /* Set left_pad to number of spaces needed to align positive
424 and negative formats */
426 int left_bytes = 0;
427 int other_left_bytes = 0;
429 /* Work out number of bytes for currency string and separator
430 preceding the value */
431 if (cs_precedes)
433 left_bytes += currency_symbol_len;
434 if (sep_by_space != 0)
435 ++left_bytes;
438 if (other_cs_precedes)
440 other_left_bytes += currency_symbol_len;
441 if (other_sep_by_space != 0)
442 ++other_left_bytes;
445 /* Work out number of bytes for the sign (or left parenthesis)
446 preceding the value */
447 if (sign_posn == 0 && is_negative)
448 ++left_bytes;
449 else if (sign_posn == 1)
450 left_bytes += strlen (sign_string);
451 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
452 left_bytes += strlen (sign_string);
454 if (other_sign_posn == 0 && !is_negative)
455 ++other_left_bytes;
456 else if (other_sign_posn == 1)
457 other_left_bytes += strlen (other_sign_string);
458 else if (other_cs_precedes &&
459 (other_sign_posn == 3 || other_sign_posn == 4))
460 other_left_bytes += strlen (other_sign_string);
462 /* Compare the number of bytes preceding the value for
463 each format, and set the padding accordingly */
464 if (other_left_bytes > left_bytes)
465 left_pad = other_left_bytes - left_bytes;
466 else
467 left_pad = 0;
470 /* Perhaps we'll someday make these things configurable so
471 better start using symbolic names now. */
472 #define left_paren '('
473 #define right_paren ')'
475 startp = dest; /* Remember start so we can compute length. */
477 while (left_pad-- > 0)
478 out_char (' ');
480 if (sign_posn == 0 && is_negative)
481 out_char (left_paren);
483 if (cs_precedes)
485 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
486 && sign_posn != 5)
488 out_string (sign_string);
489 if (sep_by_space == 2)
490 out_char (' ');
493 if (print_curr_symbol)
495 out_string (currency_symbol);
497 if (sign_posn == 4)
499 if (sep_by_space == 2)
500 out_char (space_char);
501 out_string (sign_string);
502 if (sep_by_space == 1)
503 /* POSIX.2 and SUS are not clear on this case, but C99
504 says a space follows the adjacent-symbol-and-sign */
505 out_char (' ');
507 else
508 if (sep_by_space == 1)
509 out_char (space_char);
512 else
513 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
514 && sign_posn != 4 && sign_posn != 5)
515 out_string (sign_string);
517 /* Print the number. */
518 #ifdef USE_IN_LIBIO
519 # ifdef _IO_MTSAFE_IO
520 f._sbf._f._lock = &lock;
521 # endif
522 INTUSE(_IO_init) ((_IO_FILE *) &f, 0);
523 _IO_JUMPS ((struct _IO_FILE_plus *) &f) = &_IO_str_jumps;
524 INTUSE(_IO_str_init_static) ((_IO_strfile *) &f, dest,
525 (s + maxsize) - dest, dest);
526 #else
527 memset ((void *) &f, 0, sizeof (f));
528 f.__magic = _IOMAGIC;
529 f.__mode.__write = 1;
530 /* The buffer size is one less than MAXLEN
531 so we have space for the null terminator. */
532 f.__bufp = f.__buffer = (char *) dest;
533 f.__bufsize = (s + maxsize) - dest;
534 f.__put_limit = f.__buffer + f.__bufsize;
535 f.__get_limit = f.__buffer;
536 /* After the buffer is full (MAXLEN characters have been written),
537 any more characters written will go to the bit bucket. */
538 f.__room_funcs = __default_room_functions;
539 f.__io_funcs.__write = NULL;
540 f.__seen = 1;
541 #endif
542 /* We clear the last available byte so we can find out whether
543 the numeric representation is too long. */
544 s[maxsize - 1] = '\0';
546 memset (&info, '\0', sizeof (info));
547 info.prec = right_prec;
548 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
549 info.spec = 'f';
550 info.is_long_double = is_long_double;
551 info.group = group;
552 info.pad = pad;
553 info.extra = 1; /* This means use values from LC_MONETARY. */
555 ptr = &fpnum;
556 done = __printf_fp ((FILE *) &f, &info, &ptr);
557 if (done < 0)
558 return -1;
560 if (s[maxsize - 1] != '\0')
562 __set_errno (E2BIG);
563 return -1;
566 dest += done;
568 if (!cs_precedes)
570 if (sign_posn == 3)
572 if (sep_by_space == 1)
573 out_char (' ');
574 out_string (sign_string);
577 if (print_curr_symbol)
579 if ((sign_posn == 3 && sep_by_space == 2)
580 || (sign_posn == 4 && sep_by_space == 1)
581 || (sign_posn == 2 && sep_by_space == 1)
582 || (sign_posn == 1 && sep_by_space == 1)
583 || (sign_posn == 0 && sep_by_space == 1))
584 out_char (space_char);
585 out_nstring (currency_symbol, currency_symbol_len);
586 if (sign_posn == 4)
588 if (sep_by_space == 2)
589 out_char (' ');
590 out_string (sign_string);
595 if (sign_posn == 2)
597 if (sep_by_space == 2)
598 out_char (' ');
599 out_string (sign_string);
602 if (sign_posn == 0 && is_negative)
603 out_char (right_paren);
605 /* Now test whether the output width is filled. */
606 if (dest - startp < width)
608 if (left)
609 /* We simply have to fill using spaces. */
611 out_char (' ');
612 while (dest - startp < width);
613 else
615 int dist = width - (dest - startp);
616 char *cp;
617 for (cp = dest - 1; cp >= startp; --cp)
618 cp[dist] = cp[0];
620 dest += dist;
623 startp[--dist] = ' ';
624 while (dist > 0);
629 /* Terminate the string. */
630 *dest = '\0';
632 return dest - s;
635 ssize_t
636 __strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
638 va_list ap;
640 va_start (ap, format);
642 ssize_t res = __vstrfmon_l (s, maxsize, loc, format, ap);
644 va_end (ap);
646 return res;
648 weak_alias (__strfmon_l, strfmon_l)