(root)/
glibc-2.38/
locale/
programs/
ld-monetary.c
       1  /* Copyright (C) 1995-2023 Free Software Foundation, Inc.
       2     This file is part of the GNU C Library.
       3  
       4     This program is free software; you can redistribute it and/or modify
       5     it under the terms of the GNU General Public License as published
       6     by the Free Software Foundation; version 2 of the License, or
       7     (at your option) any later version.
       8  
       9     This program is distributed in the hope that it will be useful,
      10     but WITHOUT ANY WARRANTY; without even the implied warranty of
      11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12     GNU General Public License for more details.
      13  
      14     You should have received a copy of the GNU General Public License
      15     along with this program; if not, see <https://www.gnu.org/licenses/>.  */
      16  
      17  #ifdef HAVE_CONFIG_H
      18  # include <config.h>
      19  #endif
      20  
      21  #include <byteswap.h>
      22  #include <langinfo.h>
      23  #include <limits.h>
      24  #include <stdlib.h>
      25  #include <string.h>
      26  #include <stdint.h>
      27  #include <sys/uio.h>
      28  
      29  #include <assert.h>
      30  
      31  #include "localedef.h"
      32  #include "linereader.h"
      33  #include "localeinfo.h"
      34  #include "locfile.h"
      35  
      36  
      37  /* The real definition of the struct for the LC_MONETARY locale.  */
      38  struct locale_monetary_t
      39  {
      40    const char *int_curr_symbol;
      41    const char *currency_symbol;
      42    const char *mon_decimal_point;
      43    const char *mon_thousands_sep;
      44    uint32_t mon_decimal_point_wc;
      45    uint32_t mon_thousands_sep_wc;
      46    char *mon_grouping;
      47    size_t mon_grouping_len;
      48    const char *positive_sign;
      49    const char *negative_sign;
      50    signed char int_frac_digits;
      51    signed char frac_digits;
      52    signed char p_cs_precedes;
      53    signed char p_sep_by_space;
      54    signed char n_cs_precedes;
      55    signed char n_sep_by_space;
      56    signed char p_sign_posn;
      57    signed char n_sign_posn;
      58    signed char int_p_cs_precedes;
      59    signed char int_p_sep_by_space;
      60    signed char int_n_cs_precedes;
      61    signed char int_n_sep_by_space;
      62    signed char int_p_sign_posn;
      63    signed char int_n_sign_posn;
      64    const char *duo_int_curr_symbol;
      65    const char *duo_currency_symbol;
      66    signed char duo_int_frac_digits;
      67    signed char duo_frac_digits;
      68    signed char duo_p_cs_precedes;
      69    signed char duo_p_sep_by_space;
      70    signed char duo_n_cs_precedes;
      71    signed char duo_n_sep_by_space;
      72    signed char duo_p_sign_posn;
      73    signed char duo_n_sign_posn;
      74    signed char duo_int_p_cs_precedes;
      75    signed char duo_int_p_sep_by_space;
      76    signed char duo_int_n_cs_precedes;
      77    signed char duo_int_n_sep_by_space;
      78    signed char duo_int_p_sign_posn;
      79    signed char duo_int_n_sign_posn;
      80    uint32_t uno_valid_from;
      81    uint32_t uno_valid_to;
      82    uint32_t duo_valid_from;
      83    uint32_t duo_valid_to;
      84    uint32_t conversion_rate[2];
      85    char *crncystr;
      86  };
      87  
      88  
      89  /* The content iof the field int_curr_symbol has to be taken from
      90     ISO-4217.  We test for correct values.  */
      91  #define DEFINE_INT_CURR(str) str,
      92  static const char *const valid_int_curr[] =
      93    {
      94  #   include "../iso-4217.def"
      95    };
      96  #define NR_VALID_INT_CURR ((sizeof (valid_int_curr) \
      97  			    / sizeof (valid_int_curr[0])))
      98  #undef DEFINE_INT_CURR
      99  
     100  
     101  /* Prototypes for local functions.  */
     102  static int curr_strcmp (const char *s1, const char **s2);
     103  
     104  
     105  static void
     106  monetary_startup (struct linereader *lr, struct localedef_t *locale,
     107  		  int ignore_content)
     108  {
     109    if (!ignore_content)
     110      {
     111        struct locale_monetary_t *monetary;
     112  
     113        locale->categories[LC_MONETARY].monetary = monetary =
     114  	(struct locale_monetary_t *) xmalloc (sizeof (*monetary));
     115  
     116        memset (monetary, '\0', sizeof (struct locale_monetary_t));
     117  
     118        monetary->mon_grouping = NULL;
     119        monetary->mon_grouping_len = 0;
     120  
     121        monetary->int_frac_digits = -2;
     122        monetary->frac_digits = -2;
     123        monetary->p_cs_precedes = -2;
     124        monetary->p_sep_by_space = -2;
     125        monetary->n_cs_precedes = -2;
     126        monetary->n_sep_by_space = -2;
     127        monetary->p_sign_posn = -2;
     128        monetary->n_sign_posn = -2;
     129        monetary->int_p_cs_precedes = -2;
     130        monetary->int_p_sep_by_space = -2;
     131        monetary->int_n_cs_precedes = -2;
     132        monetary->int_n_sep_by_space = -2;
     133        monetary->int_p_sign_posn = -2;
     134        monetary->int_n_sign_posn = -2;
     135        monetary->duo_int_frac_digits = -2;
     136        monetary->duo_frac_digits = -2;
     137        monetary->duo_p_cs_precedes = -2;
     138        monetary->duo_p_sep_by_space = -2;
     139        monetary->duo_n_cs_precedes = -2;
     140        monetary->duo_n_sep_by_space = -2;
     141        monetary->duo_p_sign_posn = -2;
     142        monetary->duo_n_sign_posn = -2;
     143        monetary->duo_int_p_cs_precedes = -2;
     144        monetary->duo_int_p_sep_by_space = -2;
     145        monetary->duo_int_n_cs_precedes = -2;
     146        monetary->duo_int_n_sep_by_space = -2;
     147        monetary->duo_int_p_sign_posn = -2;
     148        monetary->duo_int_n_sign_posn = -2;
     149      }
     150  
     151    if (lr != NULL)
     152      {
     153        lr->translate_strings = 1;
     154        lr->return_widestr = 0;
     155      }
     156  }
     157  
     158  
     159  void
     160  monetary_finish (struct localedef_t *locale, const struct charmap_t *charmap)
     161  {
     162    struct locale_monetary_t *monetary
     163      = locale->categories[LC_MONETARY].monetary;
     164    int nothing = 0;
     165  
     166    /* Now resolve copying and also handle completely missing definitions.  */
     167    if (monetary == NULL)
     168      {
     169        /* First see whether we were supposed to copy.  If yes, find the
     170  	 actual definition.  */
     171        if (locale->copy_name[LC_MONETARY] != NULL)
     172  	{
     173  	  /* Find the copying locale.  This has to happen transitively since
     174  	     the locale we are copying from might also copying another one.  */
     175  	  struct localedef_t *from = locale;
     176  
     177  	  do
     178  	    from = find_locale (LC_MONETARY, from->copy_name[LC_MONETARY],
     179  				from->repertoire_name, charmap);
     180  	  while (from->categories[LC_MONETARY].monetary == NULL
     181  		 && from->copy_name[LC_MONETARY] != NULL);
     182  
     183  	  monetary = locale->categories[LC_MONETARY].monetary
     184  	    = from->categories[LC_MONETARY].monetary;
     185  	}
     186  
     187        /* If there is still no definition issue a warning and create an
     188  	 empty one.  */
     189        if (monetary == NULL)
     190  	{
     191  	  record_warning (_("\
     192  No definition for %s category found"), "LC_MONETARY");
     193  	  monetary_startup (NULL, locale, 0);
     194  	  monetary = locale->categories[LC_MONETARY].monetary;
     195  	  nothing = 1;
     196  	}
     197      }
     198  
     199    /* Generally speaking there are 3 standards the define the default,
     200       warning, and error behaviour of LC_MONETARY.  They are ISO/IEC TR 30112,
     201       ISO/IEC 9899:2018 (ISO C17), and POSIX.1-2017.  Within 30112 we have the
     202       definition of a standard i18n FDCC-set, which for LC_MONETARY has the
     203       following default values:
     204  	int_curr_symbol		""
     205  	currency_symbol		""
     206  	mon_decimal_point	"<U002C>" i.e. ","
     207  	mon_thousand_sep	""
     208  	mon_grouping		"\177" i.e. CHAR_MAX
     209  	positive_sign		""
     210  	negative_sign		"<U002E>" i.e. "."
     211  	int_frac_digits		-1
     212  	frac_digits		-1
     213  	p_cs_precedes		-1
     214  	p_sep_by_space		-1
     215  	n_cs_precedes		-1
     216  	n_sep_by_space		-1
     217  	p_sign_posn		-1
     218  	n_sign_posn		-1
     219      Under 30112 a keyword that is not provided implies an empty string ""
     220      for string values or a -1 for integer values, and indicates the value
     221      is unspecified with no default implied.  No errors are considered.
     222      The exception is mon_grouping which is a string with a terminating
     223      CHAR_MAX.
     224      For POSIX Issue 7 we have:
     225      https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap07.html
     226      and again values not provided default to "" or -1, and indicate the value
     227      is not available to the locale.  The exception is mon_grouping which is
     228      a string with a terminating CHAR_MAX.  For the POSIX locale the values of
     229      LC_MONETARY should be:
     230  	int_curr_symbol		""
     231  	currency_symbol		""
     232  	mon_decimal_point	""
     233  	mon_thousands_sep	""
     234  	mon_grouping		"\177" i.e. CHAR_MAX
     235  	positive_sign		""
     236  	negative_sign		""
     237  	int_frac_digits		-1
     238  	frac_digits		-1
     239  	p_cs_precedes		-1
     240  	p_sep_by_space		-1
     241  	n_cs_precedes		-1
     242  	n_sep_by_space		-1
     243  	p_sign_posn		-1
     244  	n_sign_posn		-1
     245  	int_p_cs_precedes	-1
     246  	int_p_sep_by_space	-1
     247  	int_n_cs_precedes	-1
     248  	int_n_sep_by_space	-1
     249  	int_p_sign_posn		-1
     250  	int_n_sign_posn		-1
     251      Like with 30112, POSIX also considers no error if the keywords are
     252      missing, only that if the cateory as a whole is missing the referencing
     253      of the category results in unspecified behaviour.
     254      For ISO C17 there is no default value provided, but the localeconv
     255      specification in 7.11.2.1 admits that members of char * type may point
     256      to "" to indicate a value is not available or is of length zero.
     257      The exception is decimal_point (not mon_decimal_point) which must be a
     258      defined non-empty string.  The values of char, which are generally
     259      mapped to integer values in 30112 and POSIX, must be non-negative
     260      numbers that map to CHAR_MAX when a value is not available in the
     261      locale.
     262      In ISO C17 for the "C" locale all values are empty strings "", or
     263      CHAR_MAX, with the exception of decimal_point which is "." (defined
     264      in LC_NUMERIC).  ISO C17 makes no exception for mon_grouping like
     265      30112 and POSIX, but a value of "" is functionally equivalent to
     266      "\177" since neither defines a grouping (though the latter terminates
     267      the grouping).
     268  
     269      Lastly, we must consider the legacy C/POSIX locale that implemented
     270      as a builtin in glibc and wether a default value mapping to the
     271      C/POSIX locale may benefit the user from a compatibility perspective.
     272  
     273      Thus given 30112, POSIX, ISO C, and the builtin C/POSIX locale we
     274      need to pick appropriate defaults below.   */
     275  
     276    /* The members of LC_MONETARY are handled in the order of their definition
     277       in locale/categories.def.  Please keep them in that order.  */
     278  
     279    /* The purpose of TEST_ELEM is to define a default value for the fields
     280       in the category if the field was not defined in the cateory.  If the
     281       category was present but we didn't see a definition for the field then
     282       we also issue a warning, otherwise the only warning you get is the one
     283       earlier when a default category is created (completely missing category).
     284       This missing field warning is glibc-specific since no standard requires
     285       this warning, but we consider it valuable to print a warning for all
     286       missing fields in the category.  */
     287  #define TEST_ELEM(cat, initval) \
     288    if (monetary->cat == NULL)						      \
     289      {									      \
     290        if (! nothing)							      \
     291  	record_warning (_("%s: field `%s' not defined"),		      \
     292  			"LC_MONETARY", #cat);				      \
     293        monetary->cat = initval;						      \
     294      }
     295  
     296    /* Keyword: int_curr_symbol.  */
     297    TEST_ELEM (int_curr_symbol, "");
     298    /* The international currency symbol must come from ISO 4217.  */
     299    if (monetary->int_curr_symbol != NULL)
     300      {
     301        /* POSIX says this should be a 3-character symbol from ISO 4217
     302  	 along with a 4th character that is a divider, but the POSIX
     303  	 locale is documented as having a special case of "", and we
     304  	 support that also, so allow other locales to be created with
     305  	 a blank int_curr_symbol.  */
     306        int ics_len = strlen (monetary->int_curr_symbol);
     307        if (ics_len != 4 && ics_len != 0)
     308  	{
     309  	  if (! nothing)
     310  	    record_error (0, 0, _("\
     311  %s: value of field `int_curr_symbol' has wrong length"),
     312  			  "LC_MONETARY");
     313  	}
     314        else if (ics_len == 4)
     315  	{ /* Check the first three characters against ISO 4217 */
     316  	  char symbol[4];
     317  	  strncpy (symbol, monetary->int_curr_symbol, 3);
     318  	  symbol[3] = '\0';
     319  	  /* A user may disable this waning for testing purposes or
     320  	     for building a locale with a 3 letter country code that
     321  	     was not yet supported in our ISO 4217 list.
     322  	     See the use of --no-warnings=intcurrsym.  */
     323  	  if (bsearch (symbol, valid_int_curr, NR_VALID_INT_CURR,
     324  		       sizeof (const char *),
     325  		       (comparison_fn_t) curr_strcmp) == NULL
     326  	      && warn_int_curr_symbol)
     327  	    record_warning (_("\
     328  %s: value of field `int_curr_symbol' does \
     329  not correspond to a valid name in ISO 4217 [--no-warnings=intcurrsym]"),
     330  			    "LC_MONETARY");
     331  	}
     332      }
     333  
     334    /* Keyword: currency_symbol */
     335    TEST_ELEM (currency_symbol, "");
     336  
     337    /* Keyword: mon_decimal_point */
     338    /* ISO C17 7.11.2.1.3 explicitly allows mon_decimal_point to be the
     339       empty string e.g. "".  This indicates the value is not available in the
     340       current locale or is of zero length.  However, if the value was never
     341       defined then we issue a warning and use a glibc-specific default.  ISO
     342       30112 in the i18n FDCC-Set uses <U002C> ",", and POSIX Issue 7 in the
     343       POSIX locale uses "".  It is specific to glibc that the default is <U002E>
     344       "."; we retain this existing behaviour for backwards compatibility.  */
     345    if (monetary->mon_decimal_point == NULL)
     346      {
     347        if (! nothing)
     348  	record_warning (_("%s: field `%s' not defined, using defaults"),
     349  			"LC_MONETARY", "mon_decimal_point");
     350        monetary->mon_decimal_point = ".";
     351        monetary->mon_decimal_point_wc = L'.';
     352      }
     353  
     354    /* Keyword: mon_thousands_sep */
     355    if (monetary->mon_thousands_sep == NULL)
     356      {
     357        if (! nothing)
     358  	record_warning (_("%s: field `%s' not defined, using defaults"),
     359  			"LC_MONETARY", "mon_thousands_sep");
     360        monetary->mon_thousands_sep = "";
     361        monetary->mon_thousands_sep_wc = L'\0';
     362      }
     363  
     364    /* Keyword: mon_grouping */
     365    if (monetary->mon_grouping_len == 0)
     366      {
     367        if (! nothing)
     368  	record_warning (_("%s: field `%s' not defined"),
     369  			"LC_MONETARY", "mon_grouping");
     370        /* Missing entries are given 1 element in their bytearray with
     371  	 a value of CHAR_MAX which indicates that "No further grouping
     372  	 is to be performed" (functionally equivalent to ISO C's "C"
     373  	 locale default of ""). */
     374        monetary->mon_grouping = (char *) "\177";
     375        monetary->mon_grouping_len = 1;
     376      }
     377  
     378    /* Keyword: positive_sign */
     379    TEST_ELEM (positive_sign, "");
     380  
     381    /* Keyword: negative_sign */
     382    TEST_ELEM (negative_sign, "");
     383  
     384  #undef TEST_ELEM
     385  #define TEST_ELEM(cat, min, max, initval) \
     386    if (monetary->cat == -2)						      \
     387      {									      \
     388         if (! nothing)							      \
     389  	 record_warning (_("%s: field `%s' not defined"),		      \
     390  			 "LC_MONETARY", #cat);				      \
     391         monetary->cat = initval;						      \
     392      }									      \
     393    else if ((monetary->cat < min || monetary->cat > max)			      \
     394  	   && min < max							      \
     395  	   && !be_quiet && !nothing)					      \
     396      record_error (0, 0, _("\
     397  %s: value for field `%s' must be in range %d...%d"),			      \
     398  		  "LC_MONETARY", #cat, min, max)
     399  
     400    TEST_ELEM (int_frac_digits, 1, 0, -1);
     401    TEST_ELEM (frac_digits, 1, 0, -1);
     402    TEST_ELEM (p_cs_precedes, -1, 1, -1);
     403    TEST_ELEM (p_sep_by_space, -1, 2, -1);
     404    TEST_ELEM (n_cs_precedes, -1, 1, -1);
     405    TEST_ELEM (n_sep_by_space, -1, 2, -1);
     406    TEST_ELEM (p_sign_posn, -1, 4, -1);
     407    TEST_ELEM (n_sign_posn, -1, 4, -1);
     408  
     409    /* Keyword: crncystr */
     410    monetary->crncystr = (char *) xmalloc (strlen (monetary->currency_symbol)
     411  					 + 2);
     412    monetary->crncystr[0] = monetary->p_cs_precedes ? '-' : '+';
     413    strcpy (&monetary->crncystr[1], monetary->currency_symbol);
     414  
     415  #undef TEST_ELEM
     416  #define TEST_ELEM(cat, alt, min, max) \
     417    if (monetary->cat == -2)						      \
     418      monetary->cat = monetary->alt;					      \
     419    else if ((monetary->cat < min || monetary->cat > max)	&& ! nothing)	      \
     420      record_error (0, 0, _("\
     421  %s: value for field `%s' must be in range %d...%d"),			      \
     422  		  "LC_MONETARY", #cat, min, max)
     423  
     424    TEST_ELEM (int_p_cs_precedes, p_cs_precedes, -1, 1);
     425    TEST_ELEM (int_p_sep_by_space, p_sep_by_space, -1, 2);
     426    TEST_ELEM (int_n_cs_precedes, n_cs_precedes, -1, 1);
     427    TEST_ELEM (int_n_sep_by_space, n_sep_by_space, -1, 2);
     428    TEST_ELEM (int_p_sign_posn, p_sign_posn, -1, 4);
     429    TEST_ELEM (int_n_sign_posn, n_sign_posn, -1, 4);
     430  
     431    /* The non-POSIX.2 extensions are optional.  */
     432    if (monetary->duo_int_curr_symbol == NULL)
     433      monetary->duo_int_curr_symbol = monetary->int_curr_symbol;
     434    if (monetary->duo_currency_symbol == NULL)
     435      monetary->duo_currency_symbol = monetary->currency_symbol;
     436  
     437    if (monetary->duo_int_frac_digits == -2)
     438      monetary->duo_int_frac_digits = monetary->int_frac_digits;
     439    if (monetary->duo_frac_digits == -2)
     440      monetary->duo_frac_digits = monetary->frac_digits;
     441  
     442    TEST_ELEM (duo_p_cs_precedes, p_cs_precedes, -1, 1);
     443    TEST_ELEM (duo_p_sep_by_space, p_sep_by_space, -1, 2);
     444    TEST_ELEM (duo_n_cs_precedes, n_cs_precedes, -1, 1);
     445    TEST_ELEM (duo_n_sep_by_space, n_sep_by_space, -1, 2);
     446    TEST_ELEM (duo_int_p_cs_precedes, int_p_cs_precedes, -1, 1);
     447    TEST_ELEM (duo_int_p_sep_by_space, int_p_sep_by_space, -1, 2);
     448    TEST_ELEM (duo_int_n_cs_precedes, int_n_cs_precedes, -1, 1);
     449    TEST_ELEM (duo_int_n_sep_by_space, int_n_sep_by_space, -1, 2);
     450    TEST_ELEM (duo_p_sign_posn, p_sign_posn, -1, 4);
     451    TEST_ELEM (duo_n_sign_posn, n_sign_posn, -1, 4);
     452    TEST_ELEM (duo_int_p_sign_posn, int_p_sign_posn, -1, 4);
     453    TEST_ELEM (duo_int_n_sign_posn, int_n_sign_posn, -1, 4);
     454  
     455    if (monetary->uno_valid_from == 0)
     456      monetary->uno_valid_from = 10101;
     457    if (monetary->uno_valid_to == 0)
     458      monetary->uno_valid_to = 99991231;
     459    if (monetary->duo_valid_from == 0)
     460      monetary->duo_valid_from = 10101;
     461    if (monetary->duo_valid_to == 0)
     462      monetary->duo_valid_to = 99991231;
     463  
     464    /* Keyword: conversion_rate */
     465    if (monetary->conversion_rate[0] == 0)
     466      {
     467        monetary->conversion_rate[0] = 1;
     468        monetary->conversion_rate[1] = 1;
     469      }
     470  
     471    /* A value for monetary-decimal-point-wc was set when
     472       monetary_decimal_point was set, likewise for monetary-thousands-sep-wc.  */
     473  }
     474  
     475  
     476  void
     477  monetary_output (struct localedef_t *locale, const struct charmap_t *charmap,
     478  		 const char *output_path)
     479  {
     480    struct locale_monetary_t *monetary
     481      = locale->categories[LC_MONETARY].monetary;
     482    struct locale_file file;
     483  
     484    init_locale_data (&file, _NL_ITEM_INDEX (_NL_NUM_LC_MONETARY));
     485    add_locale_string (&file, monetary->int_curr_symbol);
     486    add_locale_string (&file, monetary->currency_symbol);
     487    add_locale_string (&file, monetary->mon_decimal_point);
     488    add_locale_string (&file, monetary->mon_thousands_sep);
     489    add_locale_raw_data (&file, monetary->mon_grouping,
     490  		       monetary->mon_grouping_len);
     491    add_locale_string (&file, monetary->positive_sign);
     492    add_locale_string (&file, monetary->negative_sign);
     493    add_locale_char (&file, monetary->int_frac_digits);
     494    add_locale_char (&file, monetary->frac_digits);
     495    add_locale_char (&file, monetary->p_cs_precedes);
     496    add_locale_char (&file, monetary->p_sep_by_space);
     497    add_locale_char (&file, monetary->n_cs_precedes);
     498    add_locale_char (&file, monetary->n_sep_by_space);
     499    add_locale_char (&file, monetary->p_sign_posn);
     500    add_locale_char (&file, monetary->n_sign_posn);
     501    add_locale_string (&file, monetary->crncystr);
     502    add_locale_char (&file, monetary->int_p_cs_precedes);
     503    add_locale_char (&file, monetary->int_p_sep_by_space);
     504    add_locale_char (&file, monetary->int_n_cs_precedes);
     505    add_locale_char (&file, monetary->int_n_sep_by_space);
     506    add_locale_char (&file, monetary->int_p_sign_posn);
     507    add_locale_char (&file, monetary->int_n_sign_posn);
     508    add_locale_string (&file, monetary->duo_int_curr_symbol);
     509    add_locale_string (&file, monetary->duo_currency_symbol);
     510    add_locale_char (&file, monetary->duo_int_frac_digits);
     511    add_locale_char (&file, monetary->duo_frac_digits);
     512    add_locale_char (&file, monetary->duo_p_cs_precedes);
     513    add_locale_char (&file, monetary->duo_p_sep_by_space);
     514    add_locale_char (&file, monetary->duo_n_cs_precedes);
     515    add_locale_char (&file, monetary->duo_n_sep_by_space);
     516    add_locale_char (&file, monetary->duo_int_p_cs_precedes);
     517    add_locale_char (&file, monetary->duo_int_p_sep_by_space);
     518    add_locale_char (&file, monetary->duo_int_n_cs_precedes);
     519    add_locale_char (&file, monetary->duo_int_n_sep_by_space);
     520    add_locale_char (&file, monetary->duo_p_sign_posn);
     521    add_locale_char (&file, monetary->duo_n_sign_posn);
     522    add_locale_char (&file, monetary->duo_int_p_sign_posn);
     523    add_locale_char (&file, monetary->duo_int_n_sign_posn);
     524    add_locale_uint32 (&file, monetary->uno_valid_from);
     525    add_locale_uint32 (&file, monetary->uno_valid_to);
     526    add_locale_uint32 (&file, monetary->duo_valid_from);
     527    add_locale_uint32 (&file, monetary->duo_valid_to);
     528    add_locale_uint32_array (&file, monetary->conversion_rate, 2);
     529    add_locale_uint32 (&file, monetary->mon_decimal_point_wc);
     530    add_locale_uint32 (&file, monetary->mon_thousands_sep_wc);
     531    add_locale_string (&file, charmap->code_set_name);
     532    write_locale_data (output_path, LC_MONETARY, "LC_MONETARY", &file);
     533  }
     534  
     535  
     536  static int
     537  curr_strcmp (const char *s1, const char **s2)
     538  {
     539    return strcmp (s1, *s2);
     540  }
     541  
     542  
     543  /* The parser for the LC_MONETARY section of the locale definition.  */
     544  void
     545  monetary_read (struct linereader *ldfile, struct localedef_t *result,
     546  	       const struct charmap_t *charmap, const char *repertoire_name,
     547  	       int ignore_content)
     548  {
     549    struct repertoire_t *repertoire = NULL;
     550    struct locale_monetary_t *monetary;
     551    struct token *now;
     552    enum token_t nowtok;
     553  
     554    /* Get the repertoire we have to use.  */
     555    if (repertoire_name != NULL)
     556      repertoire = repertoire_read (repertoire_name);
     557  
     558    /* The rest of the line containing `LC_MONETARY' must be free.  */
     559    lr_ignore_rest (ldfile, 1);
     560  
     561    do
     562      {
     563        now = lr_token (ldfile, charmap, result, NULL, verbose);
     564        nowtok = now->tok;
     565      }
     566    while (nowtok == tok_eol);
     567  
     568    /* If we see `copy' now we are almost done.  */
     569    if (nowtok == tok_copy)
     570      {
     571        handle_copy (ldfile, charmap, repertoire_name, result, tok_lc_monetary,
     572  		   LC_MONETARY, "LC_MONETARY", ignore_content);
     573        return;
     574      }
     575  
     576    /* Prepare the data structures.  */
     577    monetary_startup (ldfile, result, ignore_content);
     578    monetary = result->categories[LC_MONETARY].monetary;
     579  
     580    while (1)
     581      {
     582        /* Of course we don't proceed beyond the end of file.  */
     583        if (nowtok == tok_eof)
     584  	break;
     585  
     586        /* Ignore empty lines.  */
     587        if (nowtok == tok_eol)
     588  	{
     589  	  now = lr_token (ldfile, charmap, result, NULL, verbose);
     590  	  nowtok = now->tok;
     591  	  continue;
     592  	}
     593  
     594        switch (nowtok)
     595  	{
     596  #define STR_ELEM(cat) \
     597  	case tok_##cat:							      \
     598  	  /* Ignore the rest of the line if we don't need the input of	      \
     599  	     this line.  */						      \
     600  	  if (ignore_content)						      \
     601  	    {								      \
     602  	      lr_ignore_rest (ldfile, 0);				      \
     603  	      break;							      \
     604  	    }								      \
     605  									      \
     606  	  now = lr_token (ldfile, charmap, result, NULL, verbose);	      \
     607  	  if (now->tok != tok_string)					      \
     608  	    goto err_label;						      \
     609  	  else if (monetary->cat != NULL)				      \
     610  	    lr_error (ldfile, _("%s: field `%s' declared more than once"),    \
     611  		      "LC_MONETARY", #cat);				      \
     612  	  else if (!ignore_content && now->val.str.startmb == NULL)	      \
     613  	    {								      \
     614  	      lr_error (ldfile, _("\
     615  %s: unknown character in field `%s'"), "LC_MONETARY", #cat);		      \
     616  	      monetary->cat = "";					      \
     617  	    }								      \
     618  	  else if (!ignore_content)					      \
     619  	    monetary->cat = now->val.str.startmb;			      \
     620  	  lr_ignore_rest (ldfile, 1);					      \
     621  	  break
     622  
     623  	  STR_ELEM (int_curr_symbol);
     624  	  STR_ELEM (currency_symbol);
     625  	  STR_ELEM (positive_sign);
     626  	  STR_ELEM (negative_sign);
     627  	  STR_ELEM (duo_int_curr_symbol);
     628  	  STR_ELEM (duo_currency_symbol);
     629  
     630  #define STR_ELEM_WC(cat) \
     631  	case tok_##cat:							      \
     632  	  /* Ignore the rest of the line if we don't need the input of	      \
     633  	     this line.  */						      \
     634  	  if (ignore_content)						      \
     635  	    {								      \
     636  	      lr_ignore_rest (ldfile, 0);				      \
     637  	      break;							      \
     638  	    }								      \
     639  									      \
     640  	  ldfile->return_widestr = 1;					      \
     641  	  now = lr_token (ldfile, charmap, result, repertoire, verbose);      \
     642  	  if (now->tok != tok_string)					      \
     643  	    goto err_label;						      \
     644  	  if (monetary->cat != NULL)					      \
     645  	    lr_error (ldfile, _("\
     646  %s: field `%s' declared more than once"), "LC_MONETARY", #cat);		      \
     647  	  else if (!ignore_content && now->val.str.startmb == NULL)	      \
     648  	    {								      \
     649  	      lr_error (ldfile, _("\
     650  %s: unknown character in field `%s'"), "LC_MONETARY", #cat);		      \
     651  	      monetary->cat = "";					      \
     652  	      monetary->cat##_wc = L'\0';				      \
     653  	    }								      \
     654  	  else if (now->val.str.startwc != NULL && now->val.str.lenwc > 2)    \
     655  	    {								      \
     656  	      lr_error (ldfile, _("\
     657  %s: value for field `%s' must be a single character"), "LC_MONETARY", #cat);  \
     658  	    }								      \
     659  	  else if (!ignore_content)					      \
     660  	    {								      \
     661  	      monetary->cat = now->val.str.startmb;			      \
     662  									      \
     663  	      if (now->val.str.startwc != NULL)				      \
     664  		monetary->cat##_wc = *now->val.str.startwc;		      \
     665  	    }								      \
     666  	  ldfile->return_widestr = 0;					      \
     667  	  break
     668  
     669  	  STR_ELEM_WC (mon_decimal_point);
     670  	  STR_ELEM_WC (mon_thousands_sep);
     671  
     672  #define INT_ELEM(cat) \
     673  	case tok_##cat:							      \
     674  	  /* Ignore the rest of the line if we don't need the input of	      \
     675  	     this line.  */						      \
     676  	  if (ignore_content)						      \
     677  	    {								      \
     678  	      lr_ignore_rest (ldfile, 0);				      \
     679  	      break;							      \
     680  	    }								      \
     681  									      \
     682  	  now = lr_token (ldfile, charmap, result, NULL, verbose);	      \
     683  	  if (now->tok != tok_minus1 && now->tok != tok_number)		      \
     684  	    goto err_label;						      \
     685  	  else if (monetary->cat != -2)					      \
     686  	    lr_error (ldfile, _("%s: field `%s' declared more than once"),    \
     687  		      "LC_MONETARY", #cat);				      \
     688  	  else if (!ignore_content)					      \
     689  	    monetary->cat = now->tok == tok_minus1 ? -1 : now->val.num;	      \
     690  	  break
     691  
     692  	  INT_ELEM (int_frac_digits);
     693  	  INT_ELEM (frac_digits);
     694  	  INT_ELEM (p_cs_precedes);
     695  	  INT_ELEM (p_sep_by_space);
     696  	  INT_ELEM (n_cs_precedes);
     697  	  INT_ELEM (n_sep_by_space);
     698  	  INT_ELEM (p_sign_posn);
     699  	  INT_ELEM (n_sign_posn);
     700  	  INT_ELEM (int_p_cs_precedes);
     701  	  INT_ELEM (int_p_sep_by_space);
     702  	  INT_ELEM (int_n_cs_precedes);
     703  	  INT_ELEM (int_n_sep_by_space);
     704  	  INT_ELEM (int_p_sign_posn);
     705  	  INT_ELEM (int_n_sign_posn);
     706  	  INT_ELEM (duo_int_frac_digits);
     707  	  INT_ELEM (duo_frac_digits);
     708  	  INT_ELEM (duo_p_cs_precedes);
     709  	  INT_ELEM (duo_p_sep_by_space);
     710  	  INT_ELEM (duo_n_cs_precedes);
     711  	  INT_ELEM (duo_n_sep_by_space);
     712  	  INT_ELEM (duo_p_sign_posn);
     713  	  INT_ELEM (duo_n_sign_posn);
     714  	  INT_ELEM (duo_int_p_cs_precedes);
     715  	  INT_ELEM (duo_int_p_sep_by_space);
     716  	  INT_ELEM (duo_int_n_cs_precedes);
     717  	  INT_ELEM (duo_int_n_sep_by_space);
     718  	  INT_ELEM (duo_int_p_sign_posn);
     719  	  INT_ELEM (duo_int_n_sign_posn);
     720  	  INT_ELEM (uno_valid_from);
     721  	  INT_ELEM (uno_valid_to);
     722  	  INT_ELEM (duo_valid_from);
     723  	  INT_ELEM (duo_valid_to);
     724  
     725  	case tok_mon_grouping:
     726  	  /* Ignore the rest of the line if we don't need the input of
     727  	     this line.  */
     728  	  if (ignore_content)
     729  	    {
     730  	      lr_ignore_rest (ldfile, 0);
     731  	      break;
     732  	    }
     733  
     734  	  now = lr_token (ldfile, charmap, result, NULL, verbose);
     735  	  if (now->tok != tok_minus1 && now->tok != tok_number)
     736  	    goto err_label;
     737  	  else
     738  	    {
     739  	      size_t act = 0;
     740  	      size_t max = 10;
     741  	      char *grouping = ignore_content ? NULL : xmalloc (max);
     742  
     743  	      do
     744  		{
     745  		  if (act + 1 >= max)
     746  		    {
     747  		      max *= 2;
     748  		      grouping = xrealloc (grouping, max);
     749  		    }
     750  
     751  		  if (act > 0 && grouping[act - 1] == '\177')
     752  		    {
     753  		      lr_error (ldfile, _("\
     754  %s: `-1' must be last entry in `%s' field"),
     755  				"LC_MONETARY", "mon_grouping");
     756  		      lr_ignore_rest (ldfile, 0);
     757  		      break;
     758  		    }
     759  
     760  		  if (now->tok == tok_minus1)
     761  		    {
     762  		      if (!ignore_content)
     763  			grouping[act++] = '\177';
     764  		    }
     765  		  else if (now->val.num == 0)
     766  		    {
     767  		      /* A value of 0 disables grouping from here on but
     768  			 we must not store a NUL character since this
     769  			 terminates the string.  Use something different
     770  			 which must not be used otherwise.  */
     771  		      if (!ignore_content)
     772  			grouping[act++] = '\377';
     773  		    }
     774  		  else if (now->val.num > 126)
     775  		    lr_error (ldfile, _("\
     776  %s: values for field `%s' must be smaller than 127"),
     777  			      "LC_MONETARY", "mon_grouping");
     778  		  else if (!ignore_content)
     779  		    grouping[act++] = now->val.num;
     780  
     781  		  /* Next must be semicolon.  */
     782  		  now = lr_token (ldfile, charmap, result, NULL, verbose);
     783  		  if (now->tok != tok_semicolon)
     784  		    break;
     785  
     786  		  now = lr_token (ldfile, charmap, result, NULL, verbose);
     787  		}
     788  	      while (now->tok == tok_minus1 || now->tok == tok_number);
     789  
     790  	      if (now->tok != tok_eol)
     791  		goto err_label;
     792  
     793  	      if (!ignore_content)
     794  		{
     795  		  /* A single -1 means no grouping.  */
     796  		  if (act == 1 && grouping[0] == '\177')
     797  		    act--;
     798  		  grouping[act++] = '\0';
     799  
     800  		  monetary->mon_grouping = xrealloc (grouping, act);
     801  		  monetary->mon_grouping_len = act;
     802  		}
     803  	    }
     804  	  break;
     805  
     806  	case tok_conversion_rate:
     807  	  /* Ignore the rest of the line if we don't need the input of
     808  	     this line.  */
     809  	  if (ignore_content)
     810  	    {
     811  	      lr_ignore_rest (ldfile, 0);
     812  	      break;
     813  	    }
     814  
     815  	  now = lr_token (ldfile, charmap, result, NULL, verbose);
     816  	  if (now->tok != tok_number)
     817  	    goto err_label;
     818  	  if (now->val.num == 0)
     819  	    {
     820  	    invalid_conversion_rate:
     821  	      lr_error (ldfile, _("conversion rate value cannot be zero"));
     822  	      if (!ignore_content)
     823  		{
     824  		  monetary->conversion_rate[0] = 1;
     825  		  monetary->conversion_rate[1] = 1;
     826  		}
     827  	      break;
     828  	    }
     829  	  if (!ignore_content)
     830  	    monetary->conversion_rate[0] = now->val.num;
     831  	  /* Next must be a semicolon.  */
     832  	  now = lr_token (ldfile, charmap, result, NULL, verbose);
     833  	  if (now->tok != tok_semicolon)
     834  	    goto err_label;
     835  	  /* And another number.  */
     836  	  now = lr_token (ldfile, charmap, result, NULL, verbose);
     837  	  if (now->tok != tok_number)
     838  	    goto err_label;
     839  	  if (now->val.num == 0)
     840  	    goto invalid_conversion_rate;
     841  	  if (!ignore_content)
     842  	    monetary->conversion_rate[1] = now->val.num;
     843  	  /* The rest of the line must be empty.  */
     844  	  lr_ignore_rest (ldfile, 1);
     845  	  break;
     846  
     847  	case tok_end:
     848  	  /* Next we assume `LC_MONETARY'.  */
     849  	  now = lr_token (ldfile, charmap, result, NULL, verbose);
     850  	  if (now->tok == tok_eof)
     851  	    break;
     852  	  if (now->tok == tok_eol)
     853  	    lr_error (ldfile, _("%s: incomplete `END' line"), "LC_MONETARY");
     854  	  else if (now->tok != tok_lc_monetary)
     855  	    lr_error (ldfile, _("\
     856  %1$s: definition does not end with `END %1$s'"), "LC_MONETARY");
     857  	  lr_ignore_rest (ldfile, now->tok == tok_lc_monetary);
     858  	  return;
     859  
     860  	default:
     861  	err_label:
     862  	  SYNTAX_ERROR (_("%s: syntax error"), "LC_MONETARY");
     863  	}
     864  
     865        /* Prepare for the next round.  */
     866        now = lr_token (ldfile, charmap, result, NULL, verbose);
     867        nowtok = now->tok;
     868      }
     869  
     870    /* When we come here we reached the end of the file.  */
     871    lr_error (ldfile, _("%s: premature end of file"), "LC_MONETARY");
     872  }