1 /* Copyright (C) 1995-2022 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
monetary_startup(struct linereader * lr,struct localedef_t * locale,int ignore_content)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
monetary_finish(struct localedef_t * locale,const struct charmap_t * charmap)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
monetary_output(struct localedef_t * locale,const struct charmap_t * charmap,const char * output_path)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
curr_strcmp(const char * s1,const char ** s2)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
monetary_read(struct linereader * ldfile,struct localedef_t * result,const struct charmap_t * charmap,const char * repertoire_name,int ignore_content)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 }
873