Update.
[platform/upstream/glibc.git] / stdlib / strfmon.c
1 /* Formatting a monetary value according to the current locale.
2    Copyright (C) 1996, 1997, 1998, 1999, 2000 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4    Contributed by Ulrich Drepper <drepper@cygnus.com>
5    and Jochen Hein <Jochen.Hein@informatik.TU-Clausthal.de>, 1996.
6
7    The GNU C Library is free software; you can redistribute it and/or
8    modify it under the terms of the GNU Library General Public License as
9    published by the Free Software Foundation; either version 2 of the
10    License, or (at your option) any later version.
11
12    The GNU C Library is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Library General Public License for more details.
16
17    You should have received a copy of the GNU Library General Public
18    License along with the GNU C Library; see the file COPYING.LIB.  If not,
19    write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
20    Boston, MA 02111-1307, USA.  */
21
22 #include <ctype.h>
23 #include <errno.h>
24 #include <langinfo.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 to_digit(Ch) ((Ch) - '0')
56
57
58 /* We use this code also for the extended locale handling where the
59    function gets as an additional argument the locale which has to be
60    used.  To access the values we have to redefine the _NL_CURRENT
61    macro.  */
62 #ifdef USE_IN_EXTENDED_LOCALE_MODEL
63 # undef _NL_CURRENT
64 # define _NL_CURRENT(category, item) \
65   (current->values[_NL_ITEM_INDEX (item)].string)
66 #endif
67
68 extern int __printf_fp (FILE *, const struct printf_info *,
69                         const void **const);
70 /* This function determines the number of digit groups in the output.
71    The definition is in printf_fp.c.  */
72 extern unsigned int __guess_grouping (unsigned int intdig_max,
73                                       const char *grouping, wchar_t sepchar);
74
75
76 /* We have to overcome some problems with this implementation.  On the
77    one hand the strfmon() function is specified in XPG4 and of course
78    it has to follow this.  But on the other hand POSIX.2 specifies
79    some information in the LC_MONETARY category which should be used,
80    too.  Some of the information contradicts the information which can
81    be specified in format string.  */
82 #ifndef USE_IN_EXTENDED_LOCALE_MODEL
83 ssize_t
84 strfmon (char *s, size_t maxsize, const char *format, ...)
85 #else
86 ssize_t
87 __strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
88 #endif
89 {
90 #ifdef USE_IN_EXTENDED_LOCALE_MODEL
91   struct locale_data *current = loc->__locales[LC_MONETARY];
92 #endif
93 #ifdef USE_IN_LIBIO
94   _IO_strfile f;
95 #else
96   FILE f;
97 #endif
98   struct printf_info info;
99   va_list ap;                   /* Scan through the varargs.  */
100   char *dest;                   /* Pointer so copy the output.  */
101   const char *fmt;              /* Pointer that walks through format.  */
102
103   va_start (ap, format);
104
105   dest = s;
106   fmt = format;
107
108   /* Loop through the format-string.  */
109   while (*fmt != '\0')
110     {
111       /* The floating-point value to output.  */
112       union
113       {
114         double dbl;
115         __long_double_t ldbl;
116       }
117       fpnum;
118       int print_curr_symbol;
119       int left_prec;
120       int right_prec;
121       int group;
122       char pad;
123       int is_long_double;
124       int p_sign_posn;
125       int n_sign_posn;
126       int sign_posn;
127       int left;
128       int is_negative;
129       int sep_by_space;
130       int cs_precedes;
131       char sign_char;
132       int done;
133       const char *currency_symbol;
134       int width;
135       char *startp;
136       const void *ptr;
137
138       /* Process all character which do not introduce a format
139          specification.  */
140       if (*fmt != '%')
141         {
142           out_char (*fmt++);
143           continue;
144         }
145
146       /* "%%" means a single '%' character.  */
147       if (fmt[1] == '%')
148         {
149           out_char (*++fmt);
150           ++fmt;
151           continue;
152         }
153
154       /* Defaults for formatting.  */
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.  */
161       p_sign_posn = -1;                 /* This indicates whether the */
162       n_sign_posn = -1;                 /* '(' flag is given.  */
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                   va_end (ap);
178                   return -1;
179                 }
180               continue;
181             case '^':                   /* Don't group digits.  */
182               group = 0;
183               continue;
184             case '+':                   /* Use +/- for sign of number.  */
185               if (n_sign_posn != -1)
186                 {
187                   __set_errno (EINVAL);
188                   va_end (ap);
189                   return -1;
190                 }
191               if (*_NL_CURRENT (LC_MONETARY, P_SIGN_POSN) == '\0')
192                 p_sign_posn = 1;
193               else
194                 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
195               if (*_NL_CURRENT (LC_MONETARY, N_SIGN_POSN) == '\0')
196                 n_sign_posn = 1;
197               else
198                 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
199               continue;
200             case '(':                   /* Use ( ) for negative sign.  */
201               if (n_sign_posn != -1)
202                 {
203                   __set_errno (EINVAL);
204                   va_end (ap);
205                   return -1;
206                 }
207               p_sign_posn = 0;
208               n_sign_posn = 0;
209               continue;
210             case '!':                   /* Don't print the currency symbol.  */
211               print_curr_symbol = 0;
212               continue;
213             case '-':                   /* Print left justified.  */
214               left = 1;
215               continue;
216             default:
217               /* Will stop the loop.  */;
218             }
219           break;
220         }
221
222       /* If not specified by the format string now find the values for
223          the format specification.  */
224       if (p_sign_posn == -1)
225         p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
226       if (n_sign_posn == -1)
227         n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
228
229       if (isdigit (*fmt))
230         {
231           /* Parse field width.  */
232           width = to_digit (*fmt);
233
234           while (isdigit (*++fmt))
235             {
236               width *= 10;
237               width += to_digit (*fmt);
238             }
239
240           /* If we don't have enough room for the demanded width we
241              can stop now and return an error.  */
242           if (dest + width >= s + maxsize)
243             {
244               __set_errno (E2BIG);
245               va_end (ap);
246               return -1;
247             }
248         }
249
250       /* Recognize left precision.  */
251       if (*fmt == '#')
252         {
253           if (!isdigit (*++fmt))
254             {
255               __set_errno (EINVAL);
256               va_end (ap);
257               return -1;
258             }
259           left_prec = to_digit (*fmt);
260
261           while (isdigit (*++fmt))
262             {
263               left_prec *= 10;
264               left_prec += to_digit (*fmt);
265             }
266         }
267
268       /* Recognize right precision.  */
269       if (*fmt == '.')
270         {
271           if (!isdigit (*++fmt))
272             {
273               __set_errno (EINVAL);
274               va_end (ap);
275               return -1;
276             }
277           right_prec = to_digit (*fmt);
278
279           while (isdigit (*++fmt))
280             {
281               right_prec *= 10;
282               right_prec += to_digit (*fmt);
283             }
284         }
285
286       /* Handle modifier.  This is an extension.  */
287       if (*fmt == 'L')
288         {
289           ++fmt;
290           is_long_double = 1;
291         }
292
293       /* Handle format specifier.  */
294       switch (*fmt++)
295         {
296         case 'i':               /* Use international currency symbol.  */
297           currency_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
298           if (right_prec == -1)
299             {
300               if (*_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS) == CHAR_MAX)
301                 right_prec = 2;
302               else
303                 right_prec = *_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS);
304             }
305           break;
306         case 'n':               /* Use national currency symbol.  */
307           currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
308           if (right_prec == -1)
309             {
310               if (*_NL_CURRENT (LC_MONETARY, FRAC_DIGITS) == CHAR_MAX)
311                 right_prec = 2;
312               else
313                 right_prec = *_NL_CURRENT (LC_MONETARY, FRAC_DIGITS);
314             }
315           break;
316         default:                /* Any unrecognized format is an error.  */
317           __set_errno (EINVAL);
318           va_end (ap);
319           return -1;
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_char = *_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_char == '\0')
353             sign_char = '-';
354           cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
355           sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
356           sign_posn = n_sign_posn;
357         }
358       else
359         {
360           sign_char = *_NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
361           /* If the locale does not specify a character for the
362              positive sign we use a <SP>.  */
363           if (sign_char == '\0')
364             sign_char = ' ';
365           cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
366           sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
367           sign_posn = p_sign_posn;
368         }
369
370       /* Set default values for unspecified information.  */
371       if (cs_precedes != 0)
372         cs_precedes = 1;
373       if (sep_by_space == 127)
374         sep_by_space = 0;
375       if (left_prec == -1)
376         left_prec = 0;
377
378
379       /* Perhaps we'll someday make these things configurable so
380          better start using symbolic names now.  */
381 #define left_paren '('
382 #define right_paren ')'
383
384       startp = dest;            /* Remember start so we can compute length.  */
385
386       if (sign_posn == 0)
387         out_char (is_negative ? left_paren : ' ');
388
389       if (cs_precedes)
390         {
391           if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
392               && sign_posn != 5)
393             {
394               out_char (sign_char);
395               if (sep_by_space == 2)
396                 out_char (' ');
397             }
398
399           if (print_curr_symbol)
400             {
401               out_string (currency_symbol);
402
403               if (sign_posn == 4)
404                 {
405                   if (sep_by_space == 2)
406                     out_char (' ');
407                   out_char (sign_char);
408                 }
409               else
410                 if (sep_by_space == 1)
411                   out_char (' ');
412             }
413         }
414       else
415         if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
416             && sign_posn != 4 && sign_posn != 5)
417           out_char (sign_char);
418
419       /* Print the number.  */
420 #ifdef USE_IN_LIBIO
421       _IO_init ((_IO_FILE *) &f, 0);
422       _IO_JUMPS ((_IO_FILE *) &f) = &_IO_str_jumps;
423       _IO_str_init_static ((_IO_FILE *) &f, dest, (s + maxsize) - dest, dest);
424 #else
425       memset((void *) &f, 0, sizeof(f));
426       f.__magic = _IOMAGIC;
427       f.__mode.__write = 1;
428       /* The buffer size is one less than MAXLEN
429          so we have space for the null terminator.  */
430       f.__bufp = f.__buffer = (char *) dest;
431       f.__bufsize = (s + maxsize) - dest;
432       f.__put_limit = f.__buffer + f.__bufsize;
433       f.__get_limit = f.__buffer;
434       /* After the buffer is full (MAXLEN characters have been written),
435          any more characters written will go to the bit bucket.  */
436       f.__room_funcs = __default_room_functions;
437       f.__io_funcs.__write = NULL;
438       f.__seen = 1;
439 #endif
440       /* We clear the last available byte so we can find out whether
441          the numeric representation is too long.  */
442       s[maxsize - 1] = '\0';
443
444       info.prec = right_prec;
445       info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
446       info.spec = 'f';
447       info.is_long_double = is_long_double;
448       info.is_short = 0;
449       info.is_long = 0;
450       info.alt = 0;
451       info.space = 0;
452       info.left = left;
453       info.showsign = 0;
454       info.group = group;
455       info.pad = pad;
456       info.extra = 1;           /* This means use values from LC_MONETARY.  */
457       info.wide = 0;
458
459       ptr = &fpnum;
460       done = __printf_fp ((FILE *) &f, &info, &ptr);
461       if (done < 0)
462         {
463           va_end (ap);
464           return -1;
465         }
466
467       if (s[maxsize - 1] != '\0')
468         {
469           __set_errno (E2BIG);
470           return -1;
471         }
472
473       dest += done;
474
475       if (!cs_precedes)
476         {
477           if (sign_posn == 3)
478             {
479               if (sep_by_space == 1)
480                 out_char (' ');
481               out_char (sign_char);
482             }
483
484           if (print_curr_symbol)
485             {
486               if (sign_posn == 3 && sep_by_space == 2)
487                 out_char (' ');
488               out_string (currency_symbol);
489             }
490         }
491       else
492         if (sign_posn == 2)
493           {
494             if (sep_by_space == 2)
495               out_char (' ');
496             out_char (sign_char);
497           }
498
499       if (sign_posn == 0)
500         out_char (is_negative ? right_paren : ' ');
501
502       /* Now test whether the output width is filled.  */
503       if (dest - startp < width)
504         {
505           if (left)
506             /* We simply have to fill using spaces.  */
507             do
508               out_char (' ');
509             while (dest - startp < width);
510           else
511             {
512               int dist = width - (dest - startp);
513               char *cp;
514               for (cp = dest - 1; cp >= startp; --cp)
515                 cp[dist] = cp[0];
516
517               dest += dist;
518
519               do
520                 startp[--dist] = ' ';
521               while (dist > 0);
522             }
523         }
524     }
525
526   /* Terminate the string.  */
527   *dest = '\0';
528
529   va_end (ap);
530
531   return dest - s;
532 }