]> git.ipfire.org Git - thirdparty/glibc.git/blob - stdlib/strfmon_l.c
Update.
[thirdparty/glibc.git] / stdlib / strfmon_l.c
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.
5
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.
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
14 Lesser General Public License for more details.
15
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. */
20
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"
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 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)
62
63 #define to_digit(Ch) ((Ch) - '0')
64
65
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)
73
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);
81
82
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)
92 {
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. */
105
106 dest = s;
107 fmt = format;
108
109 /* Loop through the format-string. */
110 while (*fmt != '\0')
111 {
112 /* The floating-point value to output. */
113 union
114 {
115 double dbl;
116 __long_double_t ldbl;
117 }
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;
146
147 /* Process all character which do not introduce a format
148 specification. */
149 if (*fmt != '%')
150 {
151 out_char (*fmt++);
152 continue;
153 }
154
155 /* "%%" means a single '%' character. */
156 if (fmt[1] == '%')
157 {
158 out_char (*++fmt);
159 ++fmt;
160 continue;
161 }
162
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. */
175
176 /* Parse group characters. */
177 while (1)
178 {
179 switch (*++fmt)
180 {
181 case '=': /* Set fill character. */
182 pad = *++fmt;
183 if (pad == '\0')
184 {
185 /* Premature EOS. */
186 __set_errno (EINVAL);
187 return -1;
188 }
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)
195 {
196 __set_errno (EINVAL);
197 return -1;
198 }
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)
204 {
205 __set_errno (EINVAL);
206 return -1;
207 }
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. */;
219 }
220 break;
221 }
222
223 if (isdigit (*fmt))
224 {
225 /* Parse field width. */
226 width = to_digit (*fmt);
227
228 while (isdigit (*++fmt))
229 {
230 width *= 10;
231 width += to_digit (*fmt);
232 }
233
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)
237 {
238 __set_errno (E2BIG);
239 return -1;
240 }
241 }
242
243 /* Recognize left precision. */
244 if (*fmt == '#')
245 {
246 if (!isdigit (*++fmt))
247 {
248 __set_errno (EINVAL);
249 return -1;
250 }
251 left_prec = to_digit (*fmt);
252
253 while (isdigit (*++fmt))
254 {
255 left_prec *= 10;
256 left_prec += to_digit (*fmt);
257 }
258 }
259
260 /* Recognize right precision. */
261 if (*fmt == '.')
262 {
263 if (!isdigit (*++fmt))
264 {
265 __set_errno (EINVAL);
266 return -1;
267 }
268 right_prec = to_digit (*fmt);
269
270 while (isdigit (*++fmt))
271 {
272 right_prec *= 10;
273 right_prec += to_digit (*fmt);
274 }
275 }
276
277 /* Handle modifier. This is an extension. */
278 if (*fmt == 'L')
279 {
280 ++fmt;
281 is_long_double = 1;
282 }
283
284 /* Handle format specifier. */
285 char int_symbol[4];
286 switch (*fmt++)
287 {
288 case 'i': { /* Use international currency symbol. */
289 const char *int_curr_symbol;
290
291 int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
292 strncpy(int_symbol, int_curr_symbol, 3);
293 int_symbol[3] = '\0';
294
295 currency_symbol_len = 3;
296 currency_symbol = &int_symbol[0];
297 space_char = int_curr_symbol[3];
298 int_format = 1;
299 break;
300 }
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;
310 }
311
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);
318
319 if (right_prec == -1)
320 {
321 right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
322
323 if (right_prec == CHAR_MAX)
324 right_prec = 2;
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, 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;
362
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;
367 }
368 else
369 {
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;
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, 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;
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 == 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;
396
397 /* Check for degenerate cases */
398 if (sep_by_space == 2)
399 {
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;
405 }
406 if (other_sep_by_space == 2)
407 {
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;
413 }
414
415 /* Set the left precision and padding needed for alignment */
416 if (left_prec == -1)
417 {
418 left_prec = 0;
419 left_pad = 0;
420 }
421 else
422 {
423 /* Set left_pad to number of spaces needed to align positive
424 and negative formats */
425
426 int left_bytes = 0;
427 int other_left_bytes = 0;
428
429 /* Work out number of bytes for currency string and separator
430 preceding the value */
431 if (cs_precedes)
432 {
433 left_bytes += currency_symbol_len;
434 if (sep_by_space != 0)
435 ++left_bytes;
436 }
437
438 if (other_cs_precedes)
439 {
440 other_left_bytes += currency_symbol_len;
441 if (other_sep_by_space != 0)
442 ++other_left_bytes;
443 }
444
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);
453
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);
461
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;
468 }
469
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 ')'
474
475 startp = dest; /* Remember start so we can compute length. */
476
477 while (left_pad-- > 0)
478 out_char (' ');
479
480 if (sign_posn == 0 && is_negative)
481 out_char (left_paren);
482
483 if (cs_precedes)
484 {
485 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
486 && sign_posn != 5)
487 {
488 out_string (sign_string);
489 if (sep_by_space == 2)
490 out_char (' ');
491 }
492
493 if (print_curr_symbol)
494 {
495 out_string (currency_symbol);
496
497 if (sign_posn == 4)
498 {
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 (' ');
506 }
507 else
508 if (sep_by_space == 1)
509 out_char (space_char);
510 }
511 }
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);
516
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';
545
546 info.prec = right_prec;
547 info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
548 info.spec = 'f';
549 info.is_long_double = is_long_double;
550 info.is_short = 0;
551 info.is_long = 0;
552 info.alt = 0;
553 info.space = 0;
554 info.left = 0;
555 info.showsign = 0;
556 info.group = group;
557 info.pad = pad;
558 info.extra = 1; /* This means use values from LC_MONETARY. */
559 info.wide = 0;
560
561 ptr = &fpnum;
562 done = __printf_fp ((FILE *) &f, &info, &ptr);
563 if (done < 0)
564 return -1;
565
566 if (s[maxsize - 1] != '\0')
567 {
568 __set_errno (E2BIG);
569 return -1;
570 }
571
572 dest += done;
573
574 if (!cs_precedes)
575 {
576 if (sign_posn == 3)
577 {
578 if (sep_by_space == 1)
579 out_char (' ');
580 out_string (sign_string);
581 }
582
583 if (print_curr_symbol)
584 {
585 if ((sign_posn == 3 && sep_by_space == 2)
586 || (sign_posn == 4 && sep_by_space == 1)
587 || (sign_posn == 2 && sep_by_space == 1)
588 || (sign_posn == 1 && sep_by_space == 1)
589 || (sign_posn == 0 && sep_by_space == 1))
590 out_char (space_char);
591 out_nstring (currency_symbol, currency_symbol_len);
592 if (sign_posn == 4)
593 {
594 if (sep_by_space == 2)
595 out_char (' ');
596 out_string (sign_string);
597 }
598 }
599 }
600
601 if (sign_posn == 2)
602 {
603 if (sep_by_space == 2)
604 out_char (' ');
605 out_string (sign_string);
606 }
607
608 if (sign_posn == 0 && is_negative)
609 out_char (right_paren);
610
611 /* Now test whether the output width is filled. */
612 if (dest - startp < width)
613 {
614 if (left)
615 /* We simply have to fill using spaces. */
616 do
617 out_char (' ');
618 while (dest - startp < width);
619 else
620 {
621 int dist = width - (dest - startp);
622 char *cp;
623 for (cp = dest - 1; cp >= startp; --cp)
624 cp[dist] = cp[0];
625
626 dest += dist;
627
628 do
629 startp[--dist] = ' ';
630 while (dist > 0);
631 }
632 }
633 }
634
635 /* Terminate the string. */
636 *dest = '\0';
637
638 return dest - s;
639 }
640
641 ssize_t
642 __strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
643 {
644 va_list ap;
645
646 va_start (ap, format);
647
648 ssize_t res = __vstrfmon_l (s, maxsize, loc, format, ap);
649
650 va_end (ap);
651
652 return res;
653 }
654 weak_alias (__strfmon_l, strfmon_l)