]> git.ipfire.org Git - thirdparty/glibc.git/blame - stdlib/strfmon_l.c
Update copyright dates with scripts/update-copyrights.
[thirdparty/glibc.git] / stdlib / strfmon_l.c
CommitLineData
c84142e8 1/* Formatting a monetary value according to the given locale.
b168057a 2 Copyright (C) 1996-2015 Free Software Foundation, Inc.
c84142e8
UD
3 This file is part of the GNU C Library.
4 Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
5
6 The GNU C Library is free software; you can redistribute it and/or
41bdb6e2
AJ
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.
c84142e8
UD
10
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
41bdb6e2 14 Lesser General Public License for more details.
c84142e8 15
41bdb6e2 16 You should have received a copy of the GNU Lesser General Public
59ba27a6
PE
17 License along with the GNU C Library; if not, see
18 <http://www.gnu.org/licenses/>. */
c84142e8 19
ccadf7b5
UD
20#include <ctype.h>
21#include <errno.h>
22#include <langinfo.h>
23#include <locale.h>
24#include <monetary.h>
c6251f03
RM
25#include "../libio/libioP.h"
26#include "../libio/strfile.h"
ccadf7b5
UD
27#include <printf.h>
28#include <stdarg.h>
29#include <stdio.h>
30#include <string.h>
31#include "../locale/localeinfo.h"
da4cfe38 32
ccadf7b5
UD
33
34#define out_char(Ch) \
35 do { \
36 if (dest >= s + maxsize - 1) \
37 { \
38 __set_errno (E2BIG); \
39 va_end (ap); \
40 return -1; \
41 } \
42 *dest++ = (Ch); \
43 } while (0)
44
45#define out_string(String) \
46 do { \
47 const char *_s = (String); \
48 while (*_s) \
49 out_char (*_s++); \
50 } while (0)
51
52#define out_nstring(String, N) \
53 do { \
54 int _n = (N); \
55 const char *_s = (String); \
56 while (_n-- > 0) \
57 out_char (*_s++); \
58 } while (0)
59
60#define to_digit(Ch) ((Ch) - '0')
61
62
63/* We use this code also for the extended locale handling where the
64 function gets as an additional argument the locale which has to be
65 used. To access the values we have to redefine the _NL_CURRENT
66 macro. */
67#undef _NL_CURRENT
68#define _NL_CURRENT(category, item) \
69 (current->values[_NL_ITEM_INDEX (item)].string)
70
71extern int __printf_fp (FILE *, const struct printf_info *,
72 const void *const *);
73libc_hidden_proto (__printf_fp)
74/* This function determines the number of digit groups in the output.
75 The definition is in printf_fp.c. */
76extern unsigned int __guess_grouping (unsigned int intdig_max,
77 const char *grouping, wchar_t sepchar);
78
79
80/* We have to overcome some problems with this implementation. On the
81 one hand the strfmon() function is specified in XPG4 and of course
82 it has to follow this. But on the other hand POSIX.2 specifies
83 some information in the LC_MONETARY category which should be used,
84 too. Some of the information contradicts the information which can
85 be specified in format string. */
86ssize_t
87__vstrfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format,
88 va_list ap)
89{
f095bb72 90 struct __locale_data *current = loc->__locales[LC_MONETARY];
ccadf7b5 91 _IO_strfile f;
ccadf7b5
UD
92 struct printf_info info;
93 char *dest; /* Pointer so copy the output. */
94 const char *fmt; /* Pointer that walks through format. */
95
96 dest = s;
97 fmt = format;
98
99 /* Loop through the format-string. */
100 while (*fmt != '\0')
101 {
102 /* The floating-point value to output. */
103 union
104 {
105 double dbl;
106 __long_double_t ldbl;
107 }
108 fpnum;
109 int int_format;
110 int print_curr_symbol;
111 int left_prec;
112 int left_pad;
113 int right_prec;
114 int group;
115 char pad;
116 int is_long_double;
117 int p_sign_posn;
118 int n_sign_posn;
119 int sign_posn;
120 int other_sign_posn;
121 int left;
122 int is_negative;
123 int sep_by_space;
124 int other_sep_by_space;
125 int cs_precedes;
126 int other_cs_precedes;
127 const char *sign_string;
128 const char *other_sign_string;
129 int done;
130 const char *currency_symbol;
131 size_t currency_symbol_len;
153aa31b 132 long int width;
ccadf7b5
UD
133 char *startp;
134 const void *ptr;
135 char space_char;
136
137 /* Process all character which do not introduce a format
138 specification. */
139 if (*fmt != '%')
140 {
141 out_char (*fmt++);
142 continue;
143 }
144
145 /* "%%" means a single '%' character. */
146 if (fmt[1] == '%')
147 {
148 out_char (*++fmt);
149 ++fmt;
150 continue;
151 }
152
153 /* Defaults for formatting. */
154 int_format = 0; /* Use international curr. symbol */
155 print_curr_symbol = 1; /* Print the currency symbol. */
156 left_prec = -1; /* No left precision specified. */
157 right_prec = -1; /* No right precision specified. */
158 group = 1; /* Print digits grouped. */
159 pad = ' '; /* Fill character is <SP>. */
160 is_long_double = 0; /* Double argument by default. */
a7931fcf
AS
161 p_sign_posn = -2; /* This indicates whether the */
162 n_sign_posn = -2; /* '(' flag is given. */
ccadf7b5
UD
163 width = -1; /* No width specified so far. */
164 left = 0; /* Right justified by default. */
165
166 /* Parse group characters. */
167 while (1)
168 {
169 switch (*++fmt)
170 {
171 case '=': /* Set fill character. */
172 pad = *++fmt;
173 if (pad == '\0')
174 {
175 /* Premature EOS. */
176 __set_errno (EINVAL);
177 return -1;
178 }
179 continue;
180 case '^': /* Don't group digits. */
181 group = 0;
182 continue;
183 case '+': /* Use +/- for sign of number. */
a7931fcf 184 if (n_sign_posn != -2)
ccadf7b5
UD
185 {
186 __set_errno (EINVAL);
187 return -1;
188 }
189 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
190 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
191 continue;
192 case '(': /* Use ( ) for negative sign. */
a7931fcf 193 if (n_sign_posn != -2)
ccadf7b5
UD
194 {
195 __set_errno (EINVAL);
196 return -1;
197 }
198 p_sign_posn = 0;
199 n_sign_posn = 0;
200 continue;
201 case '!': /* Don't print the currency symbol. */
202 print_curr_symbol = 0;
203 continue;
204 case '-': /* Print left justified. */
205 left = 1;
206 continue;
207 default:
208 /* Will stop the loop. */;
209 }
210 break;
211 }
212
213 if (isdigit (*fmt))
214 {
215 /* Parse field width. */
216 width = to_digit (*fmt);
217
218 while (isdigit (*++fmt))
219 {
153aa31b
UD
220 int val = to_digit (*fmt);
221
222 if (width > LONG_MAX / 10
223 || (width == LONG_MAX && val > LONG_MAX % 10))
224 {
225 __set_errno (E2BIG);
226 return -1;
227 }
228
229 width = width * 10 + val;
ccadf7b5
UD
230 }
231
232 /* If we don't have enough room for the demanded width we
233 can stop now and return an error. */
153aa31b 234 if (width >= maxsize - (dest - s))
ccadf7b5
UD
235 {
236 __set_errno (E2BIG);
237 return -1;
238 }
239 }
240
241 /* Recognize left precision. */
242 if (*fmt == '#')
243 {
244 if (!isdigit (*++fmt))
245 {
246 __set_errno (EINVAL);
247 return -1;
248 }
249 left_prec = to_digit (*fmt);
250
251 while (isdigit (*++fmt))
252 {
253 left_prec *= 10;
254 left_prec += to_digit (*fmt);
255 }
256 }
257
258 /* Recognize right precision. */
259 if (*fmt == '.')
260 {
261 if (!isdigit (*++fmt))
262 {
263 __set_errno (EINVAL);
264 return -1;
265 }
266 right_prec = to_digit (*fmt);
267
268 while (isdigit (*++fmt))
269 {
270 right_prec *= 10;
271 right_prec += to_digit (*fmt);
272 }
273 }
274
275 /* Handle modifier. This is an extension. */
276 if (*fmt == 'L')
277 {
278 ++fmt;
c6251f03
RM
279 if (!__ldbl_is_dbl)
280 is_long_double = 1;
ccadf7b5
UD
281 }
282
283 /* Handle format specifier. */
284 char int_symbol[4];
285 switch (*fmt++)
286 {
287 case 'i': { /* Use international currency symbol. */
288 const char *int_curr_symbol;
289
290 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
291 strncpy(int_symbol, int_curr_symbol, 3);
292 int_symbol[3] = '\0';
293
294 currency_symbol_len = 3;
295 currency_symbol = &int_symbol[0];
296 space_char = int_curr_symbol[3];
297 int_format = 1;
298 break;
299 }
300 case 'n': /* Use national currency symbol. */
301 currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
302 currency_symbol_len = strlen (currency_symbol);
303 space_char = ' ';
304 int_format = 0;
305 break;
306 default: /* Any unrecognized format is an error. */
307 __set_errno (EINVAL);
308 return -1;
309 }
310
311 /* If not specified by the format string now find the values for
312 the format specification. */
a7931fcf 313 if (p_sign_posn == -2)
ccadf7b5 314 p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
a7931fcf 315 if (n_sign_posn == -2)
ccadf7b5
UD
316 n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
317
318 if (right_prec == -1)
319 {
320 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
321
a7931fcf 322 if (right_prec == '\377')
ccadf7b5
UD
323 right_prec = 2;
324 }
325
326 /* If we have to print the digits grouped determine how many
327 extra characters this means. */
328 if (group && left_prec != -1)
329 left_prec += __guess_grouping (left_prec,
330 _NL_CURRENT (LC_MONETARY, MON_GROUPING),
331 *_NL_CURRENT (LC_MONETARY,
332 MON_THOUSANDS_SEP));
333
334 /* Now it's time to get the value. */
335 if (is_long_double == 1)
336 {
337 fpnum.ldbl = va_arg (ap, long double);
338 is_negative = fpnum.ldbl < 0;
339 if (is_negative)
340 fpnum.ldbl = -fpnum.ldbl;
341 }
342 else
343 {
344 fpnum.dbl = va_arg (ap, double);
345 is_negative = fpnum.dbl < 0;
346 if (is_negative)
347 fpnum.dbl = -fpnum.dbl;
348 }
349
350 /* We now know the sign of the value and can determine the format. */
351 if (is_negative)
352 {
353 sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
354 /* If the locale does not specify a character for the
355 negative sign we use a '-'. */
356 if (*sign_string == '\0')
357 sign_string = (const char *) "-";
358 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
359 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
360 sign_posn = n_sign_posn;
361
362 other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
363 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
364 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
365 other_sign_posn = p_sign_posn;
366 }
367 else
368 {
369 sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
370 cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
371 sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
372 sign_posn = p_sign_posn;
373
374 other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
375 if (*other_sign_string == '\0')
376 other_sign_string = (const char *) "-";
377 other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
378 other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
379 other_sign_posn = n_sign_posn;
380 }
381
382 /* Set default values for unspecified information. */
383 if (cs_precedes != 0)
384 cs_precedes = 1;
385 if (other_cs_precedes != 0)
386 other_cs_precedes = 1;
a7931fcf 387 if (sep_by_space == '\377')
ccadf7b5 388 sep_by_space = 0;
a7931fcf 389 if (other_sep_by_space == '\377')
ccadf7b5 390 other_sep_by_space = 0;
a7931fcf 391 if (sign_posn == '\377')
ccadf7b5 392 sign_posn = 1;
a7931fcf 393 if (other_sign_posn == '\377')
ccadf7b5
UD
394 other_sign_posn = 1;
395
396 /* Check for degenerate cases */
397 if (sep_by_space == 2)
398 {
399 if (sign_posn == 0 ||
400 (sign_posn == 1 && !cs_precedes) ||
401 (sign_posn == 2 && cs_precedes))
402 /* sign and symbol are not adjacent, so no separator */
403 sep_by_space = 0;
404 }
405 if (other_sep_by_space == 2)
406 {
407 if (other_sign_posn == 0 ||
408 (other_sign_posn == 1 && !other_cs_precedes) ||
409 (other_sign_posn == 2 && other_cs_precedes))
410 /* sign and symbol are not adjacent, so no separator */
411 other_sep_by_space = 0;
412 }
413
414 /* Set the left precision and padding needed for alignment */
415 if (left_prec == -1)
416 {
417 left_prec = 0;
418 left_pad = 0;
419 }
420 else
421 {
422 /* Set left_pad to number of spaces needed to align positive
423 and negative formats */
424
425 int left_bytes = 0;
426 int other_left_bytes = 0;
427
428 /* Work out number of bytes for currency string and separator
429 preceding the value */
430 if (cs_precedes)
431 {
432 left_bytes += currency_symbol_len;
433 if (sep_by_space != 0)
434 ++left_bytes;
435 }
436
437 if (other_cs_precedes)
438 {
439 other_left_bytes += currency_symbol_len;
440 if (other_sep_by_space != 0)
441 ++other_left_bytes;
442 }
443
444 /* Work out number of bytes for the sign (or left parenthesis)
445 preceding the value */
446 if (sign_posn == 0 && is_negative)
447 ++left_bytes;
448 else if (sign_posn == 1)
449 left_bytes += strlen (sign_string);
450 else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
451 left_bytes += strlen (sign_string);
452
453 if (other_sign_posn == 0 && !is_negative)
454 ++other_left_bytes;
455 else if (other_sign_posn == 1)
456 other_left_bytes += strlen (other_sign_string);
457 else if (other_cs_precedes &&
458 (other_sign_posn == 3 || other_sign_posn == 4))
459 other_left_bytes += strlen (other_sign_string);
460
461 /* Compare the number of bytes preceding the value for
462 each format, and set the padding accordingly */
463 if (other_left_bytes > left_bytes)
464 left_pad = other_left_bytes - left_bytes;
465 else
466 left_pad = 0;
467 }
468
469 /* Perhaps we'll someday make these things configurable so
470 better start using symbolic names now. */
471#define left_paren '('
472#define right_paren ')'
473
474 startp = dest; /* Remember start so we can compute length. */
475
476 while (left_pad-- > 0)
477 out_char (' ');
478
479 if (sign_posn == 0 && is_negative)
480 out_char (left_paren);
481
482 if (cs_precedes)
483 {
484 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
485 && sign_posn != 5)
486 {
487 out_string (sign_string);
488 if (sep_by_space == 2)
489 out_char (' ');
490 }
491
492 if (print_curr_symbol)
c61a9cfb 493 out_string (currency_symbol);
a334319f 494
c61a9cfb
UD
495 if (sign_posn == 4)
496 {
497 if (print_curr_symbol && sep_by_space == 2)
498 out_char (space_char);
499 out_string (sign_string);
500 if (sep_by_space == 1)
501 /* POSIX.2 and SUS are not clear on this case, but C99
502 says a space follows the adjacent-symbol-and-sign */
503 out_char (' ');
ccadf7b5 504 }
c61a9cfb
UD
505 else
506 if (print_curr_symbol && sep_by_space == 1)
507 out_char (space_char);
ccadf7b5
UD
508 }
509 else
510 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
511 && sign_posn != 4 && sign_posn != 5)
512 out_string (sign_string);
513
514 /* Print the number. */
c6251f03 515#ifdef _IO_MTSAFE_IO
f521be31 516 f._sbf._f._lock = NULL;
c6251f03 517#endif
d18ea0c5 518 _IO_init (&f._sbf._f, 0);
f521be31 519 _IO_JUMPS (&f._sbf) = &_IO_str_jumps;
d18ea0c5 520 _IO_str_init_static_internal (&f, dest, (s + maxsize) - dest, dest);
ccadf7b5
UD
521 /* We clear the last available byte so we can find out whether
522 the numeric representation is too long. */
523 s[maxsize - 1] = '\0';
524
01f7e928 525 memset (&info, '\0', sizeof (info));
ccadf7b5
UD
526 info.prec = right_prec;
527 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
528 info.spec = 'f';
529 info.is_long_double = is_long_double;
ccadf7b5
UD
530 info.group = group;
531 info.pad = pad;
532 info.extra = 1; /* This means use values from LC_MONETARY. */
ccadf7b5
UD
533
534 ptr = &fpnum;
f521be31 535 done = __printf_fp (&f._sbf._f, &info, &ptr);
ccadf7b5
UD
536 if (done < 0)
537 return -1;
538
539 if (s[maxsize - 1] != '\0')
540 {
541 __set_errno (E2BIG);
542 return -1;
543 }
544
545 dest += done;
546
547 if (!cs_precedes)
548 {
549 if (sign_posn == 3)
550 {
551 if (sep_by_space == 1)
552 out_char (' ');
553 out_string (sign_string);
554 }
555
556 if (print_curr_symbol)
557 {
558 if ((sign_posn == 3 && sep_by_space == 2)
559 || (sign_posn == 4 && sep_by_space == 1)
560 || (sign_posn == 2 && sep_by_space == 1)
561 || (sign_posn == 1 && sep_by_space == 1)
562 || (sign_posn == 0 && sep_by_space == 1))
563 out_char (space_char);
564 out_nstring (currency_symbol, currency_symbol_len);
c61a9cfb 565 }
153aa31b 566
c61a9cfb
UD
567 if (sign_posn == 4)
568 {
569 if (sep_by_space == 2)
570 out_char (' ');
571 out_string (sign_string);
ccadf7b5
UD
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 {
153aa31b
UD
595 long int dist = width - (dest - startp);
596 for (char *cp = dest - 1; cp >= startp; --cp)
ccadf7b5
UD
597 cp[dist] = cp[0];
598
599 dest += dist;
600
601 do
602 startp[--dist] = ' ';
603 while (dist > 0);
604 }
605 }
606 }
607
608 /* Terminate the string. */
609 *dest = '\0';
610
611 return dest - s;
612}
613
614ssize_t
c6251f03 615___strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
ccadf7b5
UD
616{
617 va_list ap;
618
619 va_start (ap, format);
620
621 ssize_t res = __vstrfmon_l (s, maxsize, loc, format, ap);
622
623 va_end (ap);
624
625 return res;
626}
c6251f03
RM
627ldbl_strong_alias (___strfmon_l, __strfmon_l)
628ldbl_weak_alias (___strfmon_l, strfmon_l)