(__vstrfmon_l): Don't wrap when computing width.
[glibc/history.git] / stdlib / strfmon_l.c
blob8e63d459e3a15ad6777be1d613216f92f398d64e
1 /* Formatting a monetary value according to the given locale.
2 Copyright (C) 1996,1997,2002,2004,2006,2009 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 #include "../libio/libioP.h"
27 #include "../libio/strfile.h"
28 #include <printf.h>
29 #include <stdarg.h>
30 #include <stdio.h>
31 #include <string.h>
32 #include "../locale/localeinfo.h"
35 #define out_char(Ch) \
36 do { \
37 if (dest >= s + maxsize - 1) \
38 { \
39 __set_errno (E2BIG); \
40 va_end (ap); \
41 return -1; \
42 } \
43 *dest++ = (Ch); \
44 } while (0)
46 #define out_string(String) \
47 do { \
48 const char *_s = (String); \
49 while (*_s) \
50 out_char (*_s++); \
51 } while (0)
53 #define out_nstring(String, N) \
54 do { \
55 int _n = (N); \
56 const char *_s = (String); \
57 while (_n-- > 0) \
58 out_char (*_s++); \
59 } while (0)
61 #define to_digit(Ch) ((Ch) - '0')
64 /* We use this code also for the extended locale handling where the
65 function gets as an additional argument the locale which has to be
66 used. To access the values we have to redefine the _NL_CURRENT
67 macro. */
68 #undef _NL_CURRENT
69 #define _NL_CURRENT(category, item) \
70 (current->values[_NL_ITEM_INDEX (item)].string)
72 extern int __printf_fp (FILE *, const struct printf_info *,
73 const void *const *);
74 libc_hidden_proto (__printf_fp)
75 /* This function determines the number of digit groups in the output.
76 The definition is in printf_fp.c. */
77 extern unsigned int __guess_grouping (unsigned int intdig_max,
78 const char *grouping, wchar_t sepchar);
81 /* We have to overcome some problems with this implementation. On the
82 one hand the strfmon() function is specified in XPG4 and of course
83 it has to follow this. But on the other hand POSIX.2 specifies
84 some information in the LC_MONETARY category which should be used,
85 too. Some of the information contradicts the information which can
86 be specified in format string. */
87 ssize_t
88 __vstrfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format,
89 va_list ap)
91 struct locale_data *current = loc->__locales[LC_MONETARY];
92 _IO_strfile f;
93 #ifdef _IO_MTSAFE_IO
94 _IO_lock_t lock;
95 #endif
96 struct printf_info info;
97 char *dest; /* Pointer so copy the output. */
98 const char *fmt; /* Pointer that walks through format. */
100 dest = s;
101 fmt = format;
103 /* Loop through the format-string. */
104 while (*fmt != '\0')
106 /* The floating-point value to output. */
107 union
109 double dbl;
110 __long_double_t ldbl;
112 fpnum;
113 int int_format;
114 int print_curr_symbol;
115 int left_prec;
116 int left_pad;
117 int right_prec;
118 int group;
119 char pad;
120 int is_long_double;
121 int p_sign_posn;
122 int n_sign_posn;
123 int sign_posn;
124 int other_sign_posn;
125 int left;
126 int is_negative;
127 int sep_by_space;
128 int other_sep_by_space;
129 int cs_precedes;
130 int other_cs_precedes;
131 const char *sign_string;
132 const char *other_sign_string;
133 int done;
134 const char *currency_symbol;
135 size_t currency_symbol_len;
136 long int width;
137 char *startp;
138 const void *ptr;
139 char space_char;
141 /* Process all character which do not introduce a format
142 specification. */
143 if (*fmt != '%')
145 out_char (*fmt++);
146 continue;
149 /* "%%" means a single '%' character. */
150 if (fmt[1] == '%')
152 out_char (*++fmt);
153 ++fmt;
154 continue;
157 /* Defaults for formatting. */
158 int_format = 0; /* Use international curr. symbol */
159 print_curr_symbol = 1; /* Print the currency symbol. */
160 left_prec = -1; /* No left precision specified. */
161 right_prec = -1; /* No right precision specified. */
162 group = 1; /* Print digits grouped. */
163 pad = ' '; /* Fill character is <SP>. */
164 is_long_double = 0; /* Double argument by default. */
165 p_sign_posn = -1; /* This indicates whether the */
166 n_sign_posn = -1; /* '(' flag is given. */
167 width = -1; /* No width specified so far. */
168 left = 0; /* Right justified by default. */
170 /* Parse group characters. */
171 while (1)
173 switch (*++fmt)
175 case '=': /* Set fill character. */
176 pad = *++fmt;
177 if (pad == '\0')
179 /* Premature EOS. */
180 __set_errno (EINVAL);
181 return -1;
183 continue;
184 case '^': /* Don't group digits. */
185 group = 0;
186 continue;
187 case '+': /* Use +/- for sign of number. */
188 if (n_sign_posn != -1)
190 __set_errno (EINVAL);
191 return -1;
193 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
194 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
195 continue;
196 case '(': /* Use ( ) for negative sign. */
197 if (n_sign_posn != -1)
199 __set_errno (EINVAL);
200 return -1;
202 p_sign_posn = 0;
203 n_sign_posn = 0;
204 continue;
205 case '!': /* Don't print the currency symbol. */
206 print_curr_symbol = 0;
207 continue;
208 case '-': /* Print left justified. */
209 left = 1;
210 continue;
211 default:
212 /* Will stop the loop. */;
214 break;
217 if (isdigit (*fmt))
219 /* Parse field width. */
220 width = to_digit (*fmt);
222 while (isdigit (*++fmt))
224 int val = to_digit (*fmt);
226 if (width > LONG_MAX / 10
227 || (width == LONG_MAX && val > LONG_MAX % 10))
229 __set_errno (E2BIG);
230 return -1;
233 width = width * 10 + val;
236 /* If we don't have enough room for the demanded width we
237 can stop now and return an error. */
238 if (width >= maxsize - (dest - s))
240 __set_errno (E2BIG);
241 return -1;
245 /* Recognize left precision. */
246 if (*fmt == '#')
248 if (!isdigit (*++fmt))
250 __set_errno (EINVAL);
251 return -1;
253 left_prec = to_digit (*fmt);
255 while (isdigit (*++fmt))
257 left_prec *= 10;
258 left_prec += to_digit (*fmt);
262 /* Recognize right precision. */
263 if (*fmt == '.')
265 if (!isdigit (*++fmt))
267 __set_errno (EINVAL);
268 return -1;
270 right_prec = to_digit (*fmt);
272 while (isdigit (*++fmt))
274 right_prec *= 10;
275 right_prec += to_digit (*fmt);
279 /* Handle modifier. This is an extension. */
280 if (*fmt == 'L')
282 ++fmt;
283 if (!__ldbl_is_dbl)
284 is_long_double = 1;
287 /* Handle format specifier. */
288 char int_symbol[4];
289 switch (*fmt++)
291 case 'i': { /* Use international currency symbol. */
292 const char *int_curr_symbol;
294 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
295 strncpy(int_symbol, int_curr_symbol, 3);
296 int_symbol[3] = '\0';
298 currency_symbol_len = 3;
299 currency_symbol = &int_symbol[0];
300 space_char = int_curr_symbol[3];
301 int_format = 1;
302 break;
304 case 'n': /* Use national currency symbol. */
305 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
306 currency_symbol_len = strlen (currency_symbol);
307 space_char = ' ';
308 int_format = 0;
309 break;
310 default: /* Any unrecognized format is an error. */
311 __set_errno (EINVAL);
312 return -1;
315 /* If not specified by the format string now find the values for
316 the format specification. */
317 if (p_sign_posn == -1)
318 p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
319 if (n_sign_posn == -1)
320 n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
322 if (right_prec == -1)
324 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
326 if (right_prec == CHAR_MAX)
327 right_prec = 2;
330 /* If we have to print the digits grouped determine how many
331 extra characters this means. */
332 if (group && left_prec != -1)
333 left_prec += __guess_grouping (left_prec,
334 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
335 *_NL_CURRENT (LC_MONETARY,
336 MON_THOUSANDS_SEP));
338 /* Now it's time to get the value. */
339 if (is_long_double == 1)
341 fpnum.ldbl = va_arg (ap, long double);
342 is_negative = fpnum.ldbl < 0;
343 if (is_negative)
344 fpnum.ldbl = -fpnum.ldbl;
346 else
348 fpnum.dbl = va_arg (ap, double);
349 is_negative = fpnum.dbl < 0;
350 if (is_negative)
351 fpnum.dbl = -fpnum.dbl;
354 /* We now know the sign of the value and can determine the format. */
355 if (is_negative)
357 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
358 /* If the locale does not specify a character for the
359 negative sign we use a '-'. */
360 if (*sign_string == '\0')
361 sign_string = (const char *) "-";
362 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
363 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
364 sign_posn = n_sign_posn;
366 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
367 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
368 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
369 other_sign_posn = p_sign_posn;
371 else
373 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
374 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
375 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
376 sign_posn = p_sign_posn;
378 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
379 if (*other_sign_string == '\0')
380 other_sign_string = (const char *) "-";
381 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
382 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
383 other_sign_posn = n_sign_posn;
386 /* Set default values for unspecified information. */
387 if (cs_precedes != 0)
388 cs_precedes = 1;
389 if (other_cs_precedes != 0)
390 other_cs_precedes = 1;
391 if (sep_by_space == CHAR_MAX)
392 sep_by_space = 0;
393 if (other_sep_by_space == CHAR_MAX)
394 other_sep_by_space = 0;
395 if (sign_posn == CHAR_MAX)
396 sign_posn = 1;
397 if (other_sign_posn == CHAR_MAX)
398 other_sign_posn = 1;
400 /* Check for degenerate cases */
401 if (sep_by_space == 2)
403 if (sign_posn == 0 ||
404 (sign_posn == 1 && !cs_precedes) ||
405 (sign_posn == 2 && cs_precedes))
406 /* sign and symbol are not adjacent, so no separator */
407 sep_by_space = 0;
409 if (other_sep_by_space == 2)
411 if (other_sign_posn == 0 ||
412 (other_sign_posn == 1 && !other_cs_precedes) ||
413 (other_sign_posn == 2 && other_cs_precedes))
414 /* sign and symbol are not adjacent, so no separator */
415 other_sep_by_space = 0;
418 /* Set the left precision and padding needed for alignment */
419 if (left_prec == -1)
421 left_prec = 0;
422 left_pad = 0;
424 else
426 /* Set left_pad to number of spaces needed to align positive
427 and negative formats */
429 int left_bytes = 0;
430 int other_left_bytes = 0;
432 /* Work out number of bytes for currency string and separator
433 preceding the value */
434 if (cs_precedes)
436 left_bytes += currency_symbol_len;
437 if (sep_by_space != 0)
438 ++left_bytes;
441 if (other_cs_precedes)
443 other_left_bytes += currency_symbol_len;
444 if (other_sep_by_space != 0)
445 ++other_left_bytes;
448 /* Work out number of bytes for the sign (or left parenthesis)
449 preceding the value */
450 if (sign_posn == 0 && is_negative)
451 ++left_bytes;
452 else if (sign_posn == 1)
453 left_bytes += strlen (sign_string);
454 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
455 left_bytes += strlen (sign_string);
457 if (other_sign_posn == 0 && !is_negative)
458 ++other_left_bytes;
459 else if (other_sign_posn == 1)
460 other_left_bytes += strlen (other_sign_string);
461 else if (other_cs_precedes &&
462 (other_sign_posn == 3 || other_sign_posn == 4))
463 other_left_bytes += strlen (other_sign_string);
465 /* Compare the number of bytes preceding the value for
466 each format, and set the padding accordingly */
467 if (other_left_bytes > left_bytes)
468 left_pad = other_left_bytes - left_bytes;
469 else
470 left_pad = 0;
473 /* Perhaps we'll someday make these things configurable so
474 better start using symbolic names now. */
475 #define left_paren '('
476 #define right_paren ')'
478 startp = dest; /* Remember start so we can compute length. */
480 while (left_pad-- > 0)
481 out_char (' ');
483 if (sign_posn == 0 && is_negative)
484 out_char (left_paren);
486 if (cs_precedes)
488 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
489 && sign_posn != 5)
491 out_string (sign_string);
492 if (sep_by_space == 2)
493 out_char (' ');
496 if (print_curr_symbol)
497 out_string (currency_symbol);
499 if (sign_posn == 4)
501 if (print_curr_symbol && sep_by_space == 2)
502 out_char (space_char);
503 out_string (sign_string);
504 if (sep_by_space == 1)
505 /* POSIX.2 and SUS are not clear on this case, but C99
506 says a space follows the adjacent-symbol-and-sign */
507 out_char (' ');
509 else
510 if (print_curr_symbol && sep_by_space == 1)
511 out_char (space_char);
513 else
514 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
515 && sign_posn != 4 && sign_posn != 5)
516 out_string (sign_string);
518 /* Print the number. */
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 /* We clear the last available byte so we can find out whether
527 the numeric representation is too long. */
528 s[maxsize - 1] = '\0';
530 memset (&info, '\0', sizeof (info));
531 info.prec = right_prec;
532 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
533 info.spec = 'f';
534 info.is_long_double = is_long_double;
535 info.group = group;
536 info.pad = pad;
537 info.extra = 1; /* This means use values from LC_MONETARY. */
539 ptr = &fpnum;
540 done = __printf_fp ((FILE *) &f, &info, &ptr);
541 if (done < 0)
542 return -1;
544 if (s[maxsize - 1] != '\0')
546 __set_errno (E2BIG);
547 return -1;
550 dest += done;
552 if (!cs_precedes)
554 if (sign_posn == 3)
556 if (sep_by_space == 1)
557 out_char (' ');
558 out_string (sign_string);
561 if (print_curr_symbol)
563 if ((sign_posn == 3 && sep_by_space == 2)
564 || (sign_posn == 4 && sep_by_space == 1)
565 || (sign_posn == 2 && sep_by_space == 1)
566 || (sign_posn == 1 && sep_by_space == 1)
567 || (sign_posn == 0 && sep_by_space == 1))
568 out_char (space_char);
569 out_nstring (currency_symbol, currency_symbol_len);
572 if (sign_posn == 4)
574 if (sep_by_space == 2)
575 out_char (' ');
576 out_string (sign_string);
580 if (sign_posn == 2)
582 if (sep_by_space == 2)
583 out_char (' ');
584 out_string (sign_string);
587 if (sign_posn == 0 && is_negative)
588 out_char (right_paren);
590 /* Now test whether the output width is filled. */
591 if (dest - startp < width)
593 if (left)
594 /* We simply have to fill using spaces. */
596 out_char (' ');
597 while (dest - startp < width);
598 else
600 long int dist = width - (dest - startp);
601 for (char *cp = dest - 1; cp >= startp; --cp)
602 cp[dist] = cp[0];
604 dest += dist;
607 startp[--dist] = ' ';
608 while (dist > 0);
613 /* Terminate the string. */
614 *dest = '\0';
616 return dest - s;
619 ssize_t
620 ___strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
622 va_list ap;
624 va_start (ap, format);
626 ssize_t res = __vstrfmon_l (s, maxsize, loc, format, ap);
628 va_end (ap);
630 return res;
632 ldbl_strong_alias (___strfmon_l, __strfmon_l)
633 ldbl_weak_alias (___strfmon_l, strfmon_l)