update from main archive 961201
[platform/upstream/glibc.git] / stdlib / strfmon.c
1 /* strfmon -- formating a monetary value according to the current locale
2 Copyright (C) 1996 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
19 not, 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 extern int __printf_fp (FILE *, const struct printf_info *,
58                         const void **const);
59 /* This function determines the number of digit groups in the output.
60    The definition is in printf_fp.c.  */
61 extern unsigned int __guess_grouping (unsigned int intdig_max,
62                                       const char *grouping, wchar_t sepchar);
63
64
65 /* We have to overcome some problems with this implementation.  On the
66    one hand the strfmon() function is specified by in XPG4 and of
67    course it has to follow this.  But on the other hand POSIX.2
68    specifies some information in the LC_MONETARY category which should
69    be used, too.  Some of the information contradicts the information
70    which can be specified in format string.  */
71 ssize_t
72 strfmon (char *s, size_t maxsize, const char *format, ...)
73 {
74 #ifdef USE_IN_LIBIO
75   _IO_strfile f;
76 #else
77   FILE f;
78 #endif
79   struct printf_info info;
80   va_list ap;                   /* Scan through the varargs.  */
81   char *dest;                   /* Pointer so copy the output.  */
82   const char *fmt;              /* Pointer that walks through format.  */
83
84   va_start (ap, format);
85
86   dest = s;
87   fmt = format;
88
89   /* Loop through the format-string.  */
90   while (*fmt != '\0')
91     {
92       /* The floating-point value to output.  */
93       union
94       {
95         double dbl;
96         __long_double_t ldbl;
97       }
98       fpnum;
99       int print_curr_symbol;
100       int left_prec;
101       int right_prec;
102       int group;
103       char pad;
104       int is_long_double;
105       int p_sign_posn;
106       int n_sign_posn;
107       int sign_posn;
108       int left;
109       int is_negative;
110       int sep_by_space;
111       int cs_precedes;
112       char sign_char;
113       int done;
114       const char *currency_symbol;
115       int width;
116       char *startp;
117       const void *ptr;
118
119       /* Process all character which do not introduce a format
120          specification.  */
121       if (*fmt != '%')
122         {
123           out_char (*fmt++);
124           continue;
125         }
126
127       /* "%%" means a single '%' character.  */
128       if (fmt[1] == '%')
129         {
130           out_char (*++fmt);
131           ++fmt;
132           continue;
133         }
134
135       /* Defaults for formatting.  */
136       print_curr_symbol = 1;            /* Print the currency symbol.  */
137       left_prec = -1;                   /* No left precision specified.  */
138       right_prec = -1;                  /* No right precision specified.  */
139       group = 1;                        /* Print digits grouped.  */
140       pad = ' ';                        /* Fill character is <SP>.  */
141       is_long_double = 0;               /* Double argument by default.  */
142       p_sign_posn = -1;                 /* This indicates whether the */
143       n_sign_posn = -1;                 /* '(' flag is given.  */
144       width = -1;                       /* No width specified so far.  */
145       left = 0;                         /* Right justified by default.  */
146
147       /* Parse group characters.  */
148       while (1)
149         {
150           switch (*++fmt)
151             {
152             case '=':                   /* Set fill character.  */
153               pad = *++fmt;
154               continue;
155             case '^':                   /* Don't group digits.  */
156               group = 0;
157               continue;
158             case '+':                   /* Use +/- for sign of number.  */
159               if (n_sign_posn != -1)
160                 {
161                   __set_errno (EINVAL);
162                   va_end (ap);
163                   return -1;
164                 }
165               if (*_NL_CURRENT (LC_MONETARY, P_SIGN_POSN) == '\0')
166                 p_sign_posn = 1;
167               else
168                 p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
169               if (*_NL_CURRENT (LC_MONETARY, N_SIGN_POSN) == '\0')
170                 n_sign_posn = 1;
171               else
172                 n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
173               continue;
174             case '(':                   /* Use ( ) for negative sign.  */
175               if (n_sign_posn != -1)
176                 {
177                   __set_errno (EINVAL);
178                   va_end (ap);
179                   return -1;
180                 }
181               n_sign_posn = 5;  /* This is a else unused value.  */
182               continue;
183             case '!':                   /* Don't print the currency symbol.  */
184               print_curr_symbol = 0;
185               continue;
186             case '-':                   /* Print left justified.  */
187               left = 1;
188               continue;
189             default:
190               /* Will stop the loop.  */;
191             }
192           break;
193         }
194
195       if (isdigit (*fmt))
196         {
197           /* Parse field width.  */
198           width = to_digit (*fmt);
199
200           while (isdigit (*++fmt))
201             {
202               width *= 10;
203               width += to_digit (*fmt);
204             }
205
206           /* If we don't have enough room for the demanded width we
207              can stop now and return an error.  */
208           if (dest + width >= s + maxsize)
209             {
210               __set_errno (E2BIG);
211               va_end (ap);
212               return -1;
213             }
214         }
215
216       /* Recognize left precision.  */
217       if (*fmt == '#')
218         {
219           if (!isdigit (*++fmt))
220             {
221               __set_errno (EINVAL);
222               va_end (ap);
223               return -1;
224             }
225           left_prec = to_digit (*fmt);
226
227           while (isdigit (*++fmt))
228             {
229               left_prec *= 10;
230               left_prec += to_digit (*fmt);
231             }
232         }
233
234       /* Recognize right precision.  */
235       if (*fmt == '.')
236         {
237           if (!isdigit (*++fmt))
238             {
239               __set_errno (EINVAL);
240               va_end (ap);
241               return -1;
242             }
243           right_prec = to_digit (*fmt);
244
245           while (isdigit (*++fmt))
246             {
247               right_prec *= 10;
248               right_prec += to_digit (*fmt);
249             }
250         }
251
252       /* Handle modifier.  This is an extension.  */
253       if (*fmt == 'L')
254         {
255           ++fmt;
256           is_long_double = 1;
257         }
258
259       /* Handle format specifier.  */
260       switch (*fmt++)
261         {
262         case 'i':               /* Use international currency symbol.  */
263           currency_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
264           if (right_prec == -1)
265             if (*_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS) == '\177')
266               right_prec = 2;
267             else
268               right_prec = *_NL_CURRENT (LC_MONETARY, INT_FRAC_DIGITS);
269           break;
270         case 'n':               /* Use national currency symbol.  */
271           currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
272           if (right_prec == -1)
273             if (*_NL_CURRENT (LC_MONETARY, FRAC_DIGITS) == '\177')
274               right_prec = 2;
275             else
276               right_prec = *_NL_CURRENT (LC_MONETARY, FRAC_DIGITS);
277           break;
278         default:                /* Any unrecognized format is an error.  */
279           __set_errno (EINVAL);
280           va_end (ap);
281           return -1;
282         }
283
284       /* If we have to print the digits grouped determine how many
285          extra characters this means.  */
286       if (group && left_prec != -1)
287         left_prec += __guess_grouping (left_prec,
288                                        _NL_CURRENT (LC_MONETARY, MON_GROUPING),
289                                        *_NL_CURRENT (LC_MONETARY,
290                                                      MON_THOUSANDS_SEP));
291
292       /* Now it's time to get the value.  */
293       if (is_long_double == 1)
294         {
295           fpnum.ldbl = va_arg (ap, long double);
296           is_negative = fpnum.ldbl < 0;
297           if (is_negative)
298             fpnum.ldbl = -fpnum.ldbl;
299         }
300       else
301         {
302           fpnum.dbl = va_arg (ap, double);
303           is_negative = fpnum.dbl < 0;
304           if (is_negative)
305             fpnum.dbl = -fpnum.dbl;
306         }
307
308       /* We now know the sign of the value and can determine the format.  */
309       if (is_negative)
310         {
311           sign_char = *_NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
312           /* If the locale does not specify a character for the
313              negative sign we use a '-'.  */
314           if (sign_char == '\0')
315             sign_char = '-';
316           cs_precedes = *_NL_CURRENT (LC_MONETARY, N_CS_PRECEDES);
317           sep_by_space = *_NL_CURRENT (LC_MONETARY, N_SEP_BY_SPACE);
318           /* If the '(' flag is not given use the sign position from
319              the current locale.  */
320           if (n_sign_posn == -1)
321             sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
322           else
323             /* This means use parentheses.  */
324             sign_posn = 0;
325         }
326       else
327         {
328           sign_char = *_NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
329           /* If the locale does not specify a character for the
330              positive sign we use a <SP>.  */
331           if (sign_char == '\0')
332             sign_char = ' ';
333           cs_precedes = *_NL_CURRENT (LC_MONETARY, P_CS_PRECEDES);
334           sep_by_space = *_NL_CURRENT (LC_MONETARY, P_SEP_BY_SPACE);
335           if (n_sign_posn == -1)
336             sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
337           else
338             /* Here we don't set SIGN_POSN to 0 because we don'want to
339                print <SP> instead of the braces and this is what the
340                value 5 means.  */
341             sign_posn = 5;
342         }
343
344       /* Set default values for unspecified information.  */
345       if (cs_precedes != 0)
346         cs_precedes = 1;
347       if (sep_by_space == 127)
348         sep_by_space = 0;
349       if (left_prec == -1)
350         left_prec = 0;
351
352
353       /* Perhaps we'll someday make these things configurable so
354          better start using symbolic names now.  */
355 #define left_paren '('
356 #define right_paren ')'
357
358       startp = dest;            /* Remember start so we can compute lenght.  */
359
360       if (sign_posn == 0)
361         out_char (left_paren);
362       if (sign_posn == 5)       /* This is for positive number and ( flag.  */
363         out_char (' ');
364
365       if (cs_precedes)
366         {
367           if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
368               && sign_posn != 5)
369             {
370               out_char (sign_char);
371               if (sep_by_space == 2)
372                 out_char (' ');
373             }
374
375           if (print_curr_symbol)
376             {
377               out_string (currency_symbol);
378
379               if (sign_posn == 4)
380                 {
381                   if (sep_by_space == 2)
382                     out_char (' ');
383                   out_char (sign_char);
384                 }
385               else
386                 if (sep_by_space == 1)
387                   out_char (' ');
388             }
389         }
390       else
391         if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
392             && sign_posn != 4 && sign_posn != 5)
393           out_char (sign_char);
394
395       /* Print the number.  */
396 #ifdef USE_IN_LIBIO
397       _IO_init ((_IO_FILE *) &f, 0);
398       _IO_JUMPS ((_IO_FILE *) &f) = &_IO_str_jumps;
399       _IO_str_init_static ((_IO_FILE *) &f, dest, (s + maxsize) - dest, dest);
400 #else
401       memset((void *) &f, 0, sizeof(f));
402       f.__magic = _IOMAGIC;
403       f.__mode.__write = 1;
404       /* The buffer size is one less than MAXLEN
405          so we have space for the null terminator.  */
406       f.__bufp = f.__buffer = (char *) dest;
407       f.__bufsize = (s + maxsize) - dest;
408       f.__put_limit = f.__buffer + f.__bufsize;
409       f.__get_limit = f.__buffer;
410       /* After the buffer is full (MAXLEN characters have been written),
411          any more characters written will go to the bit bucket.  */
412       f.__room_funcs = __default_room_functions;
413       f.__io_funcs.__write = NULL;
414       f.__seen = 1;
415 #endif
416       /* We clear the last available byte so we can find out whether
417          the numeric representation is too long.  */
418       s[maxsize - 1] = '\0';
419
420       info.prec = right_prec;
421       info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
422       info.spec = 'f';
423       info.is_long_double = is_long_double;
424       info.is_short = 0;
425       info.is_long = 0;
426       info.alt = 0;
427       info.space = 0;
428       info.left = left;
429       info.showsign = 0;
430       info.group = group;
431       info.pad = pad;
432       info.extra = 1;           /* This means use values from LC_MONETARY.  */
433
434       ptr = &fpnum;
435       done = __printf_fp ((FILE *) &f, &info, &ptr);
436       if (done < 0)
437         {
438           va_end (ap);
439           return -1;
440         }
441
442       if (s[maxsize - 1] != '\0')
443         return -1;
444
445       dest += done;
446
447       if (!cs_precedes)
448         {
449           if (sign_posn == 3)
450             {
451               if (sep_by_space == 1)
452                 out_char (' ');
453               out_char (sign_char);
454             }
455
456           if (print_curr_symbol)
457             {
458               if (sign_posn == 3 && sep_by_space == 2)
459                 out_char (' ');
460               out_string (currency_symbol);
461             }
462         }
463       else
464         if (sign_posn == 2)
465           {
466             if (sep_by_space == 2)
467               out_char (' ');
468             out_char (sign_char);
469           }
470
471       if (sign_posn == 0)
472         out_char (right_paren);
473       if (sign_posn == 5)
474         out_char (' ');         /* This is for positive number and ( flag.  */
475
476       /* Now test whether the output width is filled.  */
477       if (dest - startp < width)
478         if (left)
479           /* We simply have to fill using spaces.  */
480           do
481             out_char (' ');
482           while (dest - startp < width);
483         else
484           {
485             int dist = width - (dest - startp);
486             char *cp;
487             for (cp = dest - 1; cp >= startp; --cp)
488               cp[dist] = cp[0];
489
490             dest += dist;
491
492             do
493               startp[--dist] = ' ';
494             while (dist > 0);
495           }
496     }
497
498   /* Terminate the string.  */
499   out_char ('\0');
500
501   va_end (ap);
502
503   return dest - s - 1;
504 }