f6d52e0b5637041d03b762f682e4a883e023d256
[kopensolaris-gnu/glibc.git] / stdlib / strfmon_l.c
1 /* Formatting a monetary value according to the given locale.
2    Copyright (C) 1996, 1997, 2002, 2004, 2006 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 #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"
33
34
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)
45
46 #define out_string(String)                                                    \
47   do {                                                                        \
48     const char *_s = (String);                                                \
49     while (*_s)                                                               \
50       out_char (*_s++);                                                       \
51   } while (0)
52
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)
60
61 #define to_digit(Ch) ((Ch) - '0')
62
63
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)
71
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);
79
80
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)
90 {
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.  */
99
100   dest = s;
101   fmt = format;
102
103   /* Loop through the format-string.  */
104   while (*fmt != '\0')
105     {
106       /* The floating-point value to output.  */
107       union
108       {
109         double dbl;
110         __long_double_t ldbl;
111       }
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       int width;
137       char *startp;
138       const void *ptr;
139       char space_char;
140
141       /* Process all character which do not introduce a format
142          specification.  */
143       if (*fmt != '%')
144         {
145           out_char (*fmt++);
146           continue;
147         }
148
149       /* "%%" means a single '%' character.  */
150       if (fmt[1] == '%')
151         {
152           out_char (*++fmt);
153           ++fmt;
154           continue;
155         }
156
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.  */
169
170       /* Parse group characters.  */
171       while (1)
172         {
173           switch (*++fmt)
174             {
175             case '=':                   /* Set fill character.  */
176               pad = *++fmt;
177               if (pad == '\0')
178                 {
179                   /* Premature EOS.  */
180                   __set_errno (EINVAL);
181                   return -1;
182                 }
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)
189                 {
190                   __set_errno (EINVAL);
191                   return -1;
192                 }
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)
198                 {
199                   __set_errno (EINVAL);
200                   return -1;
201                 }
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.  */;
213             }
214           break;
215         }
216
217       if (isdigit (*fmt))
218         {
219           /* Parse field width.  */
220           width = to_digit (*fmt);
221
222           while (isdigit (*++fmt))
223             {
224               width *= 10;
225               width += to_digit (*fmt);
226             }
227
228           /* If we don't have enough room for the demanded width we
229              can stop now and return an error.  */
230           if (dest + width >= s + maxsize)
231             {
232               __set_errno (E2BIG);
233               return -1;
234             }
235         }
236
237       /* Recognize left precision.  */
238       if (*fmt == '#')
239         {
240           if (!isdigit (*++fmt))
241             {
242               __set_errno (EINVAL);
243               return -1;
244             }
245           left_prec = to_digit (*fmt);
246
247           while (isdigit (*++fmt))
248             {
249               left_prec *= 10;
250               left_prec += to_digit (*fmt);
251             }
252         }
253
254       /* Recognize right precision.  */
255       if (*fmt == '.')
256         {
257           if (!isdigit (*++fmt))
258             {
259               __set_errno (EINVAL);
260               return -1;
261             }
262           right_prec = to_digit (*fmt);
263
264           while (isdigit (*++fmt))
265             {
266               right_prec *= 10;
267               right_prec += to_digit (*fmt);
268             }
269         }
270
271       /* Handle modifier.  This is an extension.  */
272       if (*fmt == 'L')
273         {
274           ++fmt;
275           if (!__ldbl_is_dbl)
276             is_long_double = 1;
277         }
278
279       /* Handle format specifier.  */
280       char int_symbol[4];
281       switch (*fmt++)
282         {
283         case 'i': {             /* Use international currency symbol.  */
284           const char *int_curr_symbol;
285
286           int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
287           strncpy(int_symbol, int_curr_symbol, 3);
288           int_symbol[3] = '\0';
289
290           currency_symbol_len = 3;
291           currency_symbol = &int_symbol[0];
292           space_char = int_curr_symbol[3];
293           int_format = 1;
294           break;
295         }
296         case 'n':               /* Use national currency symbol.  */
297           currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
298           currency_symbol_len = strlen (currency_symbol);
299           space_char = ' ';
300           int_format = 0;
301           break;
302         default:                /* Any unrecognized format is an error.  */
303           __set_errno (EINVAL);
304           return -1;
305         }
306
307       /* If not specified by the format string now find the values for
308          the format specification.  */
309       if (p_sign_posn == -1)
310         p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
311       if (n_sign_posn == -1)
312         n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
313
314       if (right_prec == -1)
315         {
316           right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
317
318           if (right_prec == CHAR_MAX)
319             right_prec = 2;
320         }
321
322       /* If we have to print the digits grouped determine how many
323          extra characters this means.  */
324       if (group && left_prec != -1)
325         left_prec += __guess_grouping (left_prec,
326                                        _NL_CURRENT (LC_MONETARY, MON_GROUPING),
327                                        *_NL_CURRENT (LC_MONETARY,
328                                                      MON_THOUSANDS_SEP));
329
330       /* Now it's time to get the value.  */
331       if (is_long_double == 1)
332         {
333           fpnum.ldbl = va_arg (ap, long double);
334           is_negative = fpnum.ldbl < 0;
335           if (is_negative)
336             fpnum.ldbl = -fpnum.ldbl;
337         }
338       else
339         {
340           fpnum.dbl = va_arg (ap, double);
341           is_negative = fpnum.dbl < 0;
342           if (is_negative)
343             fpnum.dbl = -fpnum.dbl;
344         }
345
346       /* We now know the sign of the value and can determine the format.  */
347       if (is_negative)
348         {
349           sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
350           /* If the locale does not specify a character for the
351              negative sign we use a '-'.  */
352           if (*sign_string == '\0')
353             sign_string = (const char *) "-";
354           cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
355           sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
356           sign_posn = n_sign_posn;
357
358           other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
359           other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
360           other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
361           other_sign_posn = p_sign_posn;
362         }
363       else
364         {
365           sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
366           cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
367           sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
368           sign_posn = p_sign_posn;
369
370           other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
371           if (*other_sign_string == '\0')
372             other_sign_string = (const char *) "-";
373           other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
374           other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
375           other_sign_posn = n_sign_posn;
376         }
377
378       /* Set default values for unspecified information.  */
379       if (cs_precedes != 0)
380         cs_precedes = 1;
381       if (other_cs_precedes != 0)
382         other_cs_precedes = 1;
383       if (sep_by_space == CHAR_MAX)
384         sep_by_space = 0;
385       if (other_sep_by_space == CHAR_MAX)
386         other_sep_by_space = 0;
387       if (sign_posn == CHAR_MAX)
388         sign_posn = 1;
389       if (other_sign_posn == CHAR_MAX)
390         other_sign_posn = 1;
391
392       /* Check for degenerate cases */
393       if (sep_by_space == 2)
394         {
395           if (sign_posn == 0 ||
396               (sign_posn == 1 && !cs_precedes) ||
397               (sign_posn == 2 && cs_precedes))
398             /* sign and symbol are not adjacent, so no separator */
399             sep_by_space = 0;
400         }
401       if (other_sep_by_space == 2)
402         {
403           if (other_sign_posn == 0 ||
404               (other_sign_posn == 1 && !other_cs_precedes) ||
405               (other_sign_posn == 2 && other_cs_precedes))
406             /* sign and symbol are not adjacent, so no separator */
407             other_sep_by_space = 0;
408         }
409
410       /* Set the left precision and padding needed for alignment */
411       if (left_prec == -1)
412         {
413           left_prec = 0;
414           left_pad = 0;
415         }
416       else
417         {
418           /* Set left_pad to number of spaces needed to align positive
419              and negative formats */
420
421           int left_bytes = 0;
422           int other_left_bytes = 0;
423
424           /* Work out number of bytes for currency string and separator
425              preceding the value */
426           if (cs_precedes)
427             {
428               left_bytes += currency_symbol_len;
429               if (sep_by_space != 0)
430                 ++left_bytes;
431             }
432
433           if (other_cs_precedes)
434             {
435               other_left_bytes += currency_symbol_len;
436               if (other_sep_by_space != 0)
437                 ++other_left_bytes;
438             }
439
440           /* Work out number of bytes for the sign (or left parenthesis)
441              preceding the value */
442           if (sign_posn == 0 && is_negative)
443             ++left_bytes;
444           else if (sign_posn == 1)
445             left_bytes += strlen (sign_string);
446           else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
447             left_bytes += strlen (sign_string);
448
449           if (other_sign_posn == 0 && !is_negative)
450             ++other_left_bytes;
451           else if (other_sign_posn == 1)
452             other_left_bytes += strlen (other_sign_string);
453           else if (other_cs_precedes &&
454                    (other_sign_posn == 3 || other_sign_posn == 4))
455             other_left_bytes += strlen (other_sign_string);
456
457           /* Compare the number of bytes preceding the value for
458              each format, and set the padding accordingly */
459           if (other_left_bytes > left_bytes)
460             left_pad = other_left_bytes - left_bytes;
461           else
462             left_pad = 0;
463         }
464
465       /* Perhaps we'll someday make these things configurable so
466          better start using symbolic names now.  */
467 #define left_paren '('
468 #define right_paren ')'
469
470       startp = dest;            /* Remember start so we can compute length.  */
471
472       while (left_pad-- > 0)
473         out_char (' ');
474
475       if (sign_posn == 0 && is_negative)
476         out_char (left_paren);
477
478       if (cs_precedes)
479         {
480           if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
481               && sign_posn != 5)
482             {
483               out_string (sign_string);
484               if (sep_by_space == 2)
485                 out_char (' ');
486             }
487
488           if (print_curr_symbol)
489             {
490               out_string (currency_symbol);
491
492               if (sign_posn == 4)
493                 {
494                   if (sep_by_space == 2)
495                     out_char (space_char);
496                   out_string (sign_string);
497                   if (sep_by_space == 1)
498                     /* POSIX.2 and SUS are not clear on this case, but C99
499                        says a space follows the adjacent-symbol-and-sign */
500                     out_char (' ');
501                 }
502               else
503                 if (sep_by_space == 1)
504                   out_char (space_char);
505             }
506         }
507       else
508         if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
509             && sign_posn != 4 && sign_posn != 5)
510           out_string (sign_string);
511
512       /* Print the number.  */
513 #ifdef _IO_MTSAFE_IO
514       f._sbf._f._lock = &lock;
515 #endif
516       INTUSE(_IO_init) ((_IO_FILE *) &f, 0);
517       _IO_JUMPS ((struct _IO_FILE_plus *) &f) = &_IO_str_jumps;
518       INTUSE(_IO_str_init_static) ((_IO_strfile *) &f, dest,
519                                    (s + maxsize) - dest, dest);
520       /* We clear the last available byte so we can find out whether
521          the numeric representation is too long.  */
522       s[maxsize - 1] = '\0';
523
524       memset (&info, '\0', sizeof (info));
525       info.prec = right_prec;
526       info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
527       info.spec = 'f';
528       info.is_long_double = is_long_double;
529       info.group = group;
530       info.pad = pad;
531       info.extra = 1;           /* This means use values from LC_MONETARY.  */
532
533       ptr = &fpnum;
534       done = __printf_fp ((FILE *) &f, &info, &ptr);
535       if (done < 0)
536         return -1;
537
538       if (s[maxsize - 1] != '\0')
539         {
540           __set_errno (E2BIG);
541           return -1;
542         }
543
544       dest += done;
545
546       if (!cs_precedes)
547         {
548           if (sign_posn == 3)
549             {
550               if (sep_by_space == 1)
551                 out_char (' ');
552               out_string (sign_string);
553             }
554
555           if (print_curr_symbol)
556             {
557               if ((sign_posn == 3 && sep_by_space == 2)
558                   || (sign_posn == 4 && sep_by_space == 1)
559                   || (sign_posn == 2 && sep_by_space == 1)
560                   || (sign_posn == 1 && sep_by_space == 1)
561                   || (sign_posn == 0 && sep_by_space == 1))
562                 out_char (space_char);
563               out_nstring (currency_symbol, currency_symbol_len);
564               if (sign_posn == 4)
565                 {
566                   if (sep_by_space == 2)
567                     out_char (' ');
568                   out_string (sign_string);
569                 }
570             }
571         }
572
573       if (sign_posn == 2)
574         {
575           if (sep_by_space == 2)
576             out_char (' ');
577           out_string (sign_string);
578         }
579
580       if (sign_posn == 0 && is_negative)
581         out_char (right_paren);
582
583       /* Now test whether the output width is filled.  */
584       if (dest - startp < width)
585         {
586           if (left)
587             /* We simply have to fill using spaces.  */
588             do
589               out_char (' ');
590             while (dest - startp < width);
591           else
592             {
593               int dist = width - (dest - startp);
594               char *cp;
595               for (cp = dest - 1; cp >= startp; --cp)
596                 cp[dist] = cp[0];
597
598               dest += dist;
599
600               do
601                 startp[--dist] = ' ';
602               while (dist > 0);
603             }
604         }
605     }
606
607   /* Terminate the string.  */
608   *dest = '\0';
609
610   return dest - s;
611 }
612
613 ssize_t
614 ___strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
615 {
616   va_list ap;
617
618   va_start (ap, format);
619
620   ssize_t res = __vstrfmon_l (s, maxsize, loc, format, ap);
621
622   va_end (ap);
623
624   return res;
625 }
626 ldbl_strong_alias (___strfmon_l, __strfmon_l)
627 ldbl_weak_alias (___strfmon_l, strfmon_l)