Back to index

glibc  2.9
strfmon_l.c
Go to the documentation of this file.
00001 /* Formatting a monetary value according to the given locale.
00002    Copyright (C) 1996, 1997, 2002, 2004, 2006 Free Software Foundation, Inc.
00003    This file is part of the GNU C Library.
00004    Contributed by Ulrich Drepper <drepper@cygnus.com>, 1996.
00005 
00006    The GNU C Library is free software; you can redistribute it and/or
00007    modify it under the terms of the GNU Lesser General Public
00008    License as published by the Free Software Foundation; either
00009    version 2.1 of the License, or (at your option) any later version.
00010 
00011    The GNU C Library is distributed in the hope that it will be useful,
00012    but WITHOUT ANY WARRANTY; without even the implied warranty of
00013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00014    Lesser General Public License for more details.
00015 
00016    You should have received a copy of the GNU Lesser General Public
00017    License along with the GNU C Library; if not, write to the Free
00018    Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
00019    02111-1307 USA.  */
00020 
00021 #include <ctype.h>
00022 #include <errno.h>
00023 #include <langinfo.h>
00024 #include <locale.h>
00025 #include <monetary.h>
00026 #include "../libio/libioP.h"
00027 #include "../libio/strfile.h"
00028 #include <printf.h>
00029 #include <stdarg.h>
00030 #include <stdio.h>
00031 #include <string.h>
00032 #include "../locale/localeinfo.h"
00033 
00034 
00035 #define out_char(Ch)                                                 \
00036   do {                                                               \
00037     if (dest >= s + maxsize - 1)                                     \
00038       {                                                                     \
00039        __set_errno (E2BIG);                                          \
00040        va_end (ap);                                                  \
00041        return -1;                                                    \
00042       }                                                                     \
00043     *dest++ = (Ch);                                                  \
00044   } while (0)
00045 
00046 #define out_string(String)                                           \
00047   do {                                                               \
00048     const char *_s = (String);                                              \
00049     while (*_s)                                                             \
00050       out_char (*_s++);                                                     \
00051   } while (0)
00052 
00053 #define out_nstring(String, N)                                              \
00054   do {                                                               \
00055     int _n = (N);                                                    \
00056     const char *_s = (String);                                              \
00057     while (_n-- > 0)                                                 \
00058       out_char (*_s++);                                                     \
00059   } while (0)
00060 
00061 #define to_digit(Ch) ((Ch) - '0')
00062 
00063 
00064 /* We use this code also for the extended locale handling where the
00065    function gets as an additional argument the locale which has to be
00066    used.  To access the values we have to redefine the _NL_CURRENT
00067    macro.  */
00068 #undef _NL_CURRENT
00069 #define _NL_CURRENT(category, item) \
00070   (current->values[_NL_ITEM_INDEX (item)].string)
00071 
00072 extern int __printf_fp (FILE *, const struct printf_info *,
00073                      const void *const *);
00074 libc_hidden_proto (__printf_fp)
00075 /* This function determines the number of digit groups in the output.
00076    The definition is in printf_fp.c.  */
00077 extern unsigned int __guess_grouping (unsigned int intdig_max,
00078                                   const char *grouping, wchar_t sepchar);
00079 
00080 
00081 /* We have to overcome some problems with this implementation.  On the
00082    one hand the strfmon() function is specified in XPG4 and of course
00083    it has to follow this.  But on the other hand POSIX.2 specifies
00084    some information in the LC_MONETARY category which should be used,
00085    too.  Some of the information contradicts the information which can
00086    be specified in format string.  */
00087 ssize_t
00088 __vstrfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format,
00089              va_list ap)
00090 {
00091   struct locale_data *current = loc->__locales[LC_MONETARY];
00092   _IO_strfile f;
00093 #ifdef _IO_MTSAFE_IO
00094   _IO_lock_t lock;
00095 #endif
00096   struct printf_info info;
00097   char *dest;               /* Pointer so copy the output.  */
00098   const char *fmt;          /* Pointer that walks through format.  */
00099 
00100   dest = s;
00101   fmt = format;
00102 
00103   /* Loop through the format-string.  */
00104   while (*fmt != '\0')
00105     {
00106       /* The floating-point value to output.  */
00107       union
00108       {
00109        double dbl;
00110        __long_double_t ldbl;
00111       }
00112       fpnum;
00113       int int_format;
00114       int print_curr_symbol;
00115       int left_prec;
00116       int left_pad;
00117       int right_prec;
00118       int group;
00119       char pad;
00120       int is_long_double;
00121       int p_sign_posn;
00122       int n_sign_posn;
00123       int sign_posn;
00124       int other_sign_posn;
00125       int left;
00126       int is_negative;
00127       int sep_by_space;
00128       int other_sep_by_space;
00129       int cs_precedes;
00130       int other_cs_precedes;
00131       const char *sign_string;
00132       const char *other_sign_string;
00133       int done;
00134       const char *currency_symbol;
00135       size_t currency_symbol_len;
00136       int width;
00137       char *startp;
00138       const void *ptr;
00139       char space_char;
00140 
00141       /* Process all character which do not introduce a format
00142         specification.  */
00143       if (*fmt != '%')
00144        {
00145          out_char (*fmt++);
00146          continue;
00147        }
00148 
00149       /* "%%" means a single '%' character.  */
00150       if (fmt[1] == '%')
00151        {
00152          out_char (*++fmt);
00153          ++fmt;
00154          continue;
00155        }
00156 
00157       /* Defaults for formatting.  */
00158       int_format = 0;                     /* Use international curr. symbol */
00159       print_curr_symbol = 1;              /* Print the currency symbol.  */
00160       left_prec = -1;                     /* No left precision specified.  */
00161       right_prec = -1;                    /* No right precision specified.  */
00162       group = 1;                   /* Print digits grouped.  */
00163       pad = ' ';                   /* Fill character is <SP>.  */
00164       is_long_double = 0;          /* Double argument by default.  */
00165       p_sign_posn = -1;                   /* This indicates whether the */
00166       n_sign_posn = -1;                   /* '(' flag is given.  */
00167       width = -1;                  /* No width specified so far.  */
00168       left = 0;                           /* Right justified by default.  */
00169 
00170       /* Parse group characters.  */
00171       while (1)
00172        {
00173          switch (*++fmt)
00174            {
00175            case '=':               /* Set fill character.  */
00176              pad = *++fmt;
00177              if (pad == '\0')
00178               {
00179                 /* Premature EOS.  */
00180                 __set_errno (EINVAL);
00181                 return -1;
00182               }
00183              continue;
00184            case '^':               /* Don't group digits.  */
00185              group = 0;
00186              continue;
00187            case '+':               /* Use +/- for sign of number.  */
00188              if (n_sign_posn != -1)
00189               {
00190                 __set_errno (EINVAL);
00191                 return -1;
00192               }
00193              p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
00194              n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
00195              continue;
00196            case '(':               /* Use ( ) for negative sign.  */
00197              if (n_sign_posn != -1)
00198               {
00199                 __set_errno (EINVAL);
00200                 return -1;
00201               }
00202              p_sign_posn = 0;
00203              n_sign_posn = 0;
00204              continue;
00205            case '!':               /* Don't print the currency symbol.  */
00206              print_curr_symbol = 0;
00207              continue;
00208            case '-':               /* Print left justified.  */
00209              left = 1;
00210              continue;
00211            default:
00212              /* Will stop the loop.  */;
00213            }
00214          break;
00215        }
00216 
00217       if (isdigit (*fmt))
00218        {
00219          /* Parse field width.  */
00220          width = to_digit (*fmt);
00221 
00222          while (isdigit (*++fmt))
00223            {
00224              width *= 10;
00225              width += to_digit (*fmt);
00226            }
00227 
00228          /* If we don't have enough room for the demanded width we
00229             can stop now and return an error.  */
00230          if (dest + width >= s + maxsize)
00231            {
00232              __set_errno (E2BIG);
00233              return -1;
00234            }
00235        }
00236 
00237       /* Recognize left precision.  */
00238       if (*fmt == '#')
00239        {
00240          if (!isdigit (*++fmt))
00241            {
00242              __set_errno (EINVAL);
00243              return -1;
00244            }
00245          left_prec = to_digit (*fmt);
00246 
00247          while (isdigit (*++fmt))
00248            {
00249              left_prec *= 10;
00250              left_prec += to_digit (*fmt);
00251            }
00252        }
00253 
00254       /* Recognize right precision.  */
00255       if (*fmt == '.')
00256        {
00257          if (!isdigit (*++fmt))
00258            {
00259              __set_errno (EINVAL);
00260              return -1;
00261            }
00262          right_prec = to_digit (*fmt);
00263 
00264          while (isdigit (*++fmt))
00265            {
00266              right_prec *= 10;
00267              right_prec += to_digit (*fmt);
00268            }
00269        }
00270 
00271       /* Handle modifier.  This is an extension.  */
00272       if (*fmt == 'L')
00273        {
00274          ++fmt;
00275          if (!__ldbl_is_dbl)
00276            is_long_double = 1;
00277        }
00278 
00279       /* Handle format specifier.  */
00280       char int_symbol[4];
00281       switch (*fmt++)
00282        {
00283        case 'i': {          /* Use international currency symbol.  */
00284          const char *int_curr_symbol;
00285 
00286          int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
00287          strncpy(int_symbol, int_curr_symbol, 3);
00288          int_symbol[3] = '\0';
00289 
00290          currency_symbol_len = 3;
00291          currency_symbol = &int_symbol[0];
00292          space_char = int_curr_symbol[3];
00293          int_format = 1;
00294          break;
00295        }
00296        case 'n':            /* Use national currency symbol.  */
00297          currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
00298          currency_symbol_len = strlen (currency_symbol);
00299          space_char = ' ';
00300          int_format = 0;
00301          break;
00302        default:             /* Any unrecognized format is an error.  */
00303          __set_errno (EINVAL);
00304          return -1;
00305        }
00306 
00307       /* If not specified by the format string now find the values for
00308         the format specification.  */
00309       if (p_sign_posn == -1)
00310        p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
00311       if (n_sign_posn == -1)
00312        n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
00313 
00314       if (right_prec == -1)
00315        {
00316          right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
00317 
00318          if (right_prec == CHAR_MAX)
00319            right_prec = 2;
00320        }
00321 
00322       /* If we have to print the digits grouped determine how many
00323         extra characters this means.  */
00324       if (group && left_prec != -1)
00325        left_prec += __guess_grouping (left_prec,
00326                                    _NL_CURRENT (LC_MONETARY, MON_GROUPING),
00327                                    *_NL_CURRENT (LC_MONETARY,
00328                                                MON_THOUSANDS_SEP));
00329 
00330       /* Now it's time to get the value.  */
00331       if (is_long_double == 1)
00332        {
00333          fpnum.ldbl = va_arg (ap, long double);
00334          is_negative = fpnum.ldbl < 0;
00335          if (is_negative)
00336            fpnum.ldbl = -fpnum.ldbl;
00337        }
00338       else
00339        {
00340          fpnum.dbl = va_arg (ap, double);
00341          is_negative = fpnum.dbl < 0;
00342          if (is_negative)
00343            fpnum.dbl = -fpnum.dbl;
00344        }
00345 
00346       /* We now know the sign of the value and can determine the format.  */
00347       if (is_negative)
00348        {
00349          sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
00350          /* If the locale does not specify a character for the
00351             negative sign we use a '-'.  */
00352          if (*sign_string == '\0')
00353            sign_string = (const char *) "-";
00354          cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
00355          sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
00356          sign_posn = n_sign_posn;
00357 
00358          other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
00359          other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
00360          other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
00361          other_sign_posn = p_sign_posn;
00362        }
00363       else
00364        {
00365          sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
00366          cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
00367          sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
00368          sign_posn = p_sign_posn;
00369 
00370          other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
00371          if (*other_sign_string == '\0')
00372            other_sign_string = (const char *) "-";
00373          other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
00374          other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
00375          other_sign_posn = n_sign_posn;
00376        }
00377 
00378       /* Set default values for unspecified information.  */
00379       if (cs_precedes != 0)
00380        cs_precedes = 1;
00381       if (other_cs_precedes != 0)
00382        other_cs_precedes = 1;
00383       if (sep_by_space == CHAR_MAX)
00384        sep_by_space = 0;
00385       if (other_sep_by_space == CHAR_MAX)
00386        other_sep_by_space = 0;
00387       if (sign_posn == CHAR_MAX)
00388        sign_posn = 1;
00389       if (other_sign_posn == CHAR_MAX)
00390        other_sign_posn = 1;
00391 
00392       /* Check for degenerate cases */
00393       if (sep_by_space == 2)
00394        {
00395          if (sign_posn == 0 ||
00396              (sign_posn == 1 && !cs_precedes) ||
00397              (sign_posn == 2 && cs_precedes))
00398            /* sign and symbol are not adjacent, so no separator */
00399            sep_by_space = 0;
00400        }
00401       if (other_sep_by_space == 2)
00402        {
00403          if (other_sign_posn == 0 ||
00404              (other_sign_posn == 1 && !other_cs_precedes) ||
00405              (other_sign_posn == 2 && other_cs_precedes))
00406            /* sign and symbol are not adjacent, so no separator */
00407            other_sep_by_space = 0;
00408        }
00409 
00410       /* Set the left precision and padding needed for alignment */
00411       if (left_prec == -1)
00412        {
00413          left_prec = 0;
00414          left_pad = 0;
00415        }
00416       else
00417        {
00418          /* Set left_pad to number of spaces needed to align positive
00419             and negative formats */
00420 
00421          int left_bytes = 0;
00422          int other_left_bytes = 0;
00423 
00424          /* Work out number of bytes for currency string and separator
00425             preceding the value */
00426          if (cs_precedes)
00427            {
00428              left_bytes += currency_symbol_len;
00429              if (sep_by_space != 0)
00430               ++left_bytes;
00431            }
00432 
00433          if (other_cs_precedes)
00434            {
00435              other_left_bytes += currency_symbol_len;
00436              if (other_sep_by_space != 0)
00437               ++other_left_bytes;
00438            }
00439 
00440          /* Work out number of bytes for the sign (or left parenthesis)
00441             preceding the value */
00442          if (sign_posn == 0 && is_negative)
00443            ++left_bytes;
00444          else if (sign_posn == 1)
00445            left_bytes += strlen (sign_string);
00446          else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
00447            left_bytes += strlen (sign_string);
00448 
00449          if (other_sign_posn == 0 && !is_negative)
00450            ++other_left_bytes;
00451          else if (other_sign_posn == 1)
00452            other_left_bytes += strlen (other_sign_string);
00453          else if (other_cs_precedes &&
00454                  (other_sign_posn == 3 || other_sign_posn == 4))
00455            other_left_bytes += strlen (other_sign_string);
00456 
00457          /* Compare the number of bytes preceding the value for
00458             each format, and set the padding accordingly */
00459          if (other_left_bytes > left_bytes)
00460            left_pad = other_left_bytes - left_bytes;
00461          else
00462            left_pad = 0;
00463        }
00464 
00465       /* Perhaps we'll someday make these things configurable so
00466         better start using symbolic names now.  */
00467 #define left_paren '('
00468 #define right_paren ')'
00469 
00470       startp = dest;        /* Remember start so we can compute length.  */
00471 
00472       while (left_pad-- > 0)
00473        out_char (' ');
00474 
00475       if (sign_posn == 0 && is_negative)
00476        out_char (left_paren);
00477 
00478       if (cs_precedes)
00479        {
00480          if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
00481              && sign_posn != 5)
00482            {
00483              out_string (sign_string);
00484              if (sep_by_space == 2)
00485               out_char (' ');
00486            }
00487 
00488          if (print_curr_symbol)
00489            out_string (currency_symbol);
00490 
00491          if (sign_posn == 4)
00492            {
00493              if (print_curr_symbol && sep_by_space == 2)
00494               out_char (space_char);
00495              out_string (sign_string);
00496              if (sep_by_space == 1)
00497               /* POSIX.2 and SUS are not clear on this case, but C99
00498                  says a space follows the adjacent-symbol-and-sign */
00499               out_char (' ');
00500            }
00501          else
00502            if (print_curr_symbol && sep_by_space == 1)
00503              out_char (space_char);
00504        }
00505       else
00506        if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
00507            && sign_posn != 4 && sign_posn != 5)
00508          out_string (sign_string);
00509 
00510       /* Print the number.  */
00511 #ifdef _IO_MTSAFE_IO
00512       f._sbf._f._lock = &lock;
00513 #endif
00514       INTUSE(_IO_init) ((_IO_FILE *) &f, 0);
00515       _IO_JUMPS ((struct _IO_FILE_plus *) &f) = &_IO_str_jumps;
00516       INTUSE(_IO_str_init_static) ((_IO_strfile *) &f, dest,
00517                                (s + maxsize) - dest, dest);
00518       /* We clear the last available byte so we can find out whether
00519         the numeric representation is too long.  */
00520       s[maxsize - 1] = '\0';
00521 
00522       memset (&info, '\0', sizeof (info));
00523       info.prec = right_prec;
00524       info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
00525       info.spec = 'f';
00526       info.is_long_double = is_long_double;
00527       info.group = group;
00528       info.pad = pad;
00529       info.extra = 1;              /* This means use values from LC_MONETARY.  */
00530 
00531       ptr = &fpnum;
00532       done = __printf_fp ((FILE *) &f, &info, &ptr);
00533       if (done < 0)
00534        return -1;
00535 
00536       if (s[maxsize - 1] != '\0')
00537        {
00538          __set_errno (E2BIG);
00539          return -1;
00540        }
00541 
00542       dest += done;
00543 
00544       if (!cs_precedes)
00545        {
00546          if (sign_posn == 3)
00547            {
00548              if (sep_by_space == 1)
00549               out_char (' ');
00550              out_string (sign_string);
00551            }
00552 
00553          if (print_curr_symbol)
00554            {
00555              if ((sign_posn == 3 && sep_by_space == 2)
00556                 || (sign_posn == 4 && sep_by_space == 1)
00557                 || (sign_posn == 2 && sep_by_space == 1)
00558                 || (sign_posn == 1 && sep_by_space == 1)
00559                 || (sign_posn == 0 && sep_by_space == 1))
00560               out_char (space_char);
00561              out_nstring (currency_symbol, currency_symbol_len);
00562            }
00563            
00564          if (sign_posn == 4)
00565            {
00566              if (sep_by_space == 2)
00567               out_char (' ');
00568              out_string (sign_string);
00569            }
00570        }
00571 
00572       if (sign_posn == 2)
00573        {
00574          if (sep_by_space == 2)
00575            out_char (' ');
00576          out_string (sign_string);
00577        }
00578 
00579       if (sign_posn == 0 && is_negative)
00580        out_char (right_paren);
00581 
00582       /* Now test whether the output width is filled.  */
00583       if (dest - startp < width)
00584        {
00585          if (left)
00586            /* We simply have to fill using spaces.  */
00587            do
00588              out_char (' ');
00589            while (dest - startp < width);
00590          else
00591            {
00592              int dist = width - (dest - startp);
00593              char *cp;
00594              for (cp = dest - 1; cp >= startp; --cp)
00595               cp[dist] = cp[0];
00596 
00597              dest += dist;
00598 
00599              do
00600               startp[--dist] = ' ';
00601              while (dist > 0);
00602            }
00603        }
00604     }
00605 
00606   /* Terminate the string.  */
00607   *dest = '\0';
00608 
00609   return dest - s;
00610 }
00611 
00612 ssize_t
00613 ___strfmon_l (char *s, size_t maxsize, __locale_t loc, const char *format, ...)
00614 {
00615   va_list ap;
00616 
00617   va_start (ap, format);
00618 
00619   ssize_t res = __vstrfmon_l (s, maxsize, loc, format, ap);
00620 
00621   va_end (ap);
00622 
00623   return res;
00624 }
00625 ldbl_strong_alias (___strfmon_l, __strfmon_l)
00626 ldbl_weak_alias (___strfmon_l, strfmon_l)