xref: /netbsd-src/external/gpl2/gettext/dist/gettext-tools/src/format-java.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
1 /* Java format strings.
2    Copyright (C) 2001-2004, 2006 Free Software Foundation, Inc.
3    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
4 
5    This program is free software; you can redistribute it and/or modify
6    it under the terms of the GNU General Public License as published by
7    the Free Software Foundation; either version 2, or (at your option)
8    any later version.
9 
10    This program is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13    GNU General Public License for more details.
14 
15    You should have received a copy of the GNU General Public License
16    along with this program; if not, write to the Free Software Foundation,
17    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18 
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 #include <alloca.h>
23 
24 #include <stdbool.h>
25 #include <stdlib.h>
26 #include <string.h>
27 
28 #include "format.h"
29 #include "c-ctype.h"
30 #include "xalloc.h"
31 #include "xallocsa.h"
32 #include "xvasprintf.h"
33 #include "format-invalid.h"
34 #include "gettext.h"
35 
36 #define _(str) gettext (str)
37 
38 /* Java format strings are described in java/text/MessageFormat.html.
39    See also the ICU documentation class_MessageFormat.html.
40 
41    messageFormatPattern := string ( "{" messageFormatElement "}" string )*
42 
43    messageFormatElement := argument { "," elementFormat }
44 
45    elementFormat := "time" { "," datetimeStyle }
46                   | "date" { "," datetimeStyle }
47                   | "number" { "," numberStyle }
48                   | "choice" { "," choiceStyle }
49 
50    datetimeStyle := "short"
51                     | "medium"
52                     | "long"
53                     | "full"
54                     | dateFormatPattern
55 
56    numberStyle := "currency"
57                  | "percent"
58                  | "integer"
59                  | numberFormatPattern
60 
61    choiceStyle := choiceFormatPattern
62 
63    dateFormatPattern see SimpleDateFormat.applyPattern
64 
65    numberFormatPattern see DecimalFormat.applyPattern
66 
67    choiceFormatPattern see ChoiceFormat constructor
68 
69    In strings, literal curly braces can be used if quoted between single
70    quotes.  A real single quote is represented by ''.
71 
72    If a pattern is used, then unquoted braces in the pattern, if any, must
73    match: that is, "ab {0} de" and "ab '}' de" are ok, but "ab {0'}' de" and
74    "ab } de" are not.
75 
76    The argument is a number from 0 to 9, which corresponds to the arguments
77    presented in an array to be formatted.
78 
79    It is ok to have unused arguments in the array.
80 
81    Adding a dateFormatPattern / numberFormatPattern / choiceFormatPattern
82    to an elementFormat is equivalent to creating a SimpleDateFormat /
83    DecimalFormat / ChoiceFormat and use of setFormat. For example,
84 
85      MessageFormat form =
86        new MessageFormat("The disk \"{1}\" contains {0,choice,0#no files|1#one file|2#{0,number} files}.");
87 
88    is equivalent to
89 
90      MessageFormat form = new MessageFormat("The disk \"{1}\" contains {0}.");
91      form.setFormat(1, // Number of {} occurrence in the string!
92                     new ChoiceFormat(new double[] { 0, 1, 2 },
93                                      new String[] { "no files", "one file",
94                                                     "{0,number} files" }));
95 
96    Note: The behaviour of quotes inside a choiceFormatPattern is not clear.
97    Example 1:
98      "abc{1,choice,0#{1,number,00';'000}}def"
99        JDK 1.1.x: exception
100        JDK 1.3.x: behaves like "abc{1,choice,0#{1,number,00;000}}def"
101    Example 2:
102      "abc{1,choice,0#{1,number,00';'}}def"
103        JDK 1.1.x: interprets the semicolon as number suffix
104        JDK 1.3.x: behaves like "abc{1,choice,0#{1,number,00;}}def"
105  */
106 
107 enum format_arg_type
108 {
109   FAT_NONE,
110   FAT_OBJECT,	/* java.lang.Object */
111   FAT_NUMBER,	/* java.lang.Number */
112   FAT_DATE	/* java.util.Date */
113 };
114 
115 struct numbered_arg
116 {
117   unsigned int number;
118   enum format_arg_type type;
119 };
120 
121 struct spec
122 {
123   unsigned int directives;
124   unsigned int numbered_arg_count;
125   unsigned int allocated;
126   struct numbered_arg *numbered;
127 };
128 
129 
130 /* Forward declaration of local functions.  */
131 static bool date_format_parse (const char *format);
132 static bool number_format_parse (const char *format);
133 static bool choice_format_parse (const char *format, struct spec *spec,
134 				 char **invalid_reason);
135 
136 
137 /* Quote handling:
138    - When we see a single-quote, ignore it, but toggle the quoting flag.
139    - When we see a double single-quote, ignore the first of the two.
140    Assumes local variables format, quoting.  */
141 #define HANDLE_QUOTE \
142   if (*format == '\'' && *++format != '\'') \
143     quoting = !quoting;
144 
145 /* Note that message_format_parse and choice_format_parse are mutually
146    recursive.  This is because MessageFormat can use some ChoiceFormats,
147    and a ChoiceFormat is made up from several MessageFormats.  */
148 
149 /* Return true if a format is a valid messageFormatPattern.
150    Extracts argument type information into spec.  */
151 static bool
message_format_parse(const char * format,struct spec * spec,char ** invalid_reason)152 message_format_parse (const char *format, struct spec *spec,
153 		      char **invalid_reason)
154 {
155   bool quoting = false;
156 
157   for (;;)
158     {
159       HANDLE_QUOTE;
160       if (!quoting && *format == '{')
161 	{
162 	  unsigned int depth;
163 	  const char *element_start;
164 	  const char *element_end;
165 	  size_t n;
166 	  char *element_alloced;
167 	  char *element;
168 	  unsigned int number;
169 	  enum format_arg_type type;
170 
171 	  spec->directives++;
172 
173 	  element_start = ++format;
174 	  depth = 0;
175 	  for (; *format != '\0'; format++)
176 	    {
177 	      if (*format == '{')
178 		depth++;
179 	      else if (*format == '}')
180 		{
181 		  if (depth == 0)
182 		    break;
183 		  else
184 		    depth--;
185 		}
186 	    }
187 	  if (*format == '\0')
188 	    {
189 	      *invalid_reason =
190 		xstrdup (_("The string ends in the middle of a directive: found '{' without matching '}'."));
191 	      return false;
192 	    }
193 	  element_end = format++;
194 
195 	  n = element_end - element_start;
196 	  element = element_alloced = (char *) xallocsa (n + 1);
197 	  memcpy (element, element_start, n);
198 	  element[n] = '\0';
199 
200 	  if (!c_isdigit (*element))
201 	    {
202 	      *invalid_reason =
203 		xasprintf (_("In the directive number %u, '{' is not followed by an argument number."), spec->directives);
204 	      freesa (element_alloced);
205 	      return false;
206 	    }
207 	  number = 0;
208 	  do
209 	    {
210 	      number = 10 * number + (*element - '0');
211 	      element++;
212 	    }
213 	  while (c_isdigit (*element));
214 
215 	  type = FAT_OBJECT;
216 	  if (*element == '\0')
217 	    ;
218 	  else if (strncmp (element, ",time", 5) == 0
219 		   || strncmp (element, ",date", 5) == 0)
220 	    {
221 	      type = FAT_DATE;
222 	      element += 5;
223 	      if (*element == '\0')
224 		;
225 	      else if (*element == ',')
226 		{
227 		  element++;
228 		  if (strcmp (element, "short") == 0
229 		      || strcmp (element, "medium") == 0
230 		      || strcmp (element, "long") == 0
231 		      || strcmp (element, "full") == 0
232 		      || date_format_parse (element))
233 		    ;
234 		  else
235 		    {
236 		      *invalid_reason =
237 			xasprintf (_("In the directive number %u, the substring \"%s\" is not a valid date/time style."), spec->directives, element);
238 		      freesa (element_alloced);
239 		      return false;
240 		    }
241 		}
242 	      else
243 		{
244 		  *element = '\0';
245 		  element -= 4;
246 		  *invalid_reason =
247 		    xasprintf (_("In the directive number %u, \"%s\" is not followed by a comma."), spec->directives, element);
248 		  freesa (element_alloced);
249 		  return false;
250 		}
251 	    }
252 	  else if (strncmp (element, ",number", 7) == 0)
253 	    {
254 	      type = FAT_NUMBER;
255 	      element += 7;
256 	      if (*element == '\0')
257 		;
258 	      else if (*element == ',')
259 		{
260 		  element++;
261 		  if (strcmp (element, "currency") == 0
262 		      || strcmp (element, "percent") == 0
263 		      || strcmp (element, "integer") == 0
264 		      || number_format_parse (element))
265 		    ;
266 		  else
267 		    {
268 		      *invalid_reason =
269 			xasprintf (_("In the directive number %u, the substring \"%s\" is not a valid number style."), spec->directives, element);
270 		      freesa (element_alloced);
271 		      return false;
272 		    }
273 		}
274 	      else
275 		{
276 		  *element = '\0';
277 		  element -= 6;
278 		  *invalid_reason =
279 		    xasprintf (_("In the directive number %u, \"%s\" is not followed by a comma."), spec->directives, element);
280 		  freesa (element_alloced);
281 		  return false;
282 		}
283 	    }
284 	  else if (strncmp (element, ",choice", 7) == 0)
285 	    {
286 	      type = FAT_NUMBER; /* because ChoiceFormat extends NumberFormat */
287 	      element += 7;
288 	      if (*element == '\0')
289 		;
290 	      else if (*element == ',')
291 		{
292 		  element++;
293 		  if (choice_format_parse (element, spec, invalid_reason))
294 		    ;
295 		  else
296 		    {
297 		      freesa (element_alloced);
298 		      return false;
299 		    }
300 		}
301 	      else
302 		{
303 		  *element = '\0';
304 		  element -= 6;
305 		  *invalid_reason =
306 		    xasprintf (_("In the directive number %u, \"%s\" is not followed by a comma."), spec->directives, element);
307 		  freesa (element_alloced);
308 		  return false;
309 		}
310 	    }
311 	  else
312 	    {
313 	      *invalid_reason =
314 		xasprintf (_("In the directive number %u, the argument number is not followed by a comma and one of \"%s\", \"%s\", \"%s\", \"%s\"."), spec->directives, "time", "date", "number", "choice");
315 	      freesa (element_alloced);
316 	      return false;
317 	    }
318 	  freesa (element_alloced);
319 
320 	  if (spec->allocated == spec->numbered_arg_count)
321 	    {
322 	      spec->allocated = 2 * spec->allocated + 1;
323 	      spec->numbered = (struct numbered_arg *) xrealloc (spec->numbered, spec->allocated * sizeof (struct numbered_arg));
324 	    }
325 	  spec->numbered[spec->numbered_arg_count].number = number;
326 	  spec->numbered[spec->numbered_arg_count].type = type;
327 	  spec->numbered_arg_count++;
328 	}
329       /* The doc says "ab}de" is invalid.  Even though JDK accepts it.  */
330       else if (!quoting && *format == '}')
331 	{
332 	  *invalid_reason =
333 	    xstrdup (_("The string starts in the middle of a directive: found '}' without matching '{'."));
334 	  return false;
335 	}
336       else if (*format != '\0')
337 	format++;
338       else
339 	break;
340     }
341 
342   return true;
343 }
344 
345 /* Return true if a format is a valid dateFormatPattern.  */
346 static bool
date_format_parse(const char * format)347 date_format_parse (const char *format)
348 {
349   /* Any string is valid.  Single-quote starts a quoted section, to be
350      terminated at the next single-quote or string end.  Double single-quote
351      gives a single single-quote.  Non-quoted ASCII letters are first grouped
352      into blocks of equal letters.  Then each block (e.g. 'yyyy') is
353      interpreted according to some rules.  */
354   return true;
355 }
356 
357 /* Return true if a format is a valid numberFormatPattern.  */
358 static bool
number_format_parse(const char * format)359 number_format_parse (const char *format)
360 {
361   /* Pattern Syntax:
362        pattern     := pos_pattern{';' neg_pattern}
363        pos_pattern := {prefix}number{suffix}
364        neg_pattern := {prefix}number{suffix}
365        number      := integer{'.' fraction}{exponent}
366        prefix      := '\u0000'..'\uFFFD' - special_characters
367        suffix      := '\u0000'..'\uFFFD' - special_characters
368        integer     := min_int | '#' | '#' integer | '#' ',' integer
369        min_int     := '0' | '0' min_int | '0' ',' min_int
370        fraction    := '0'* '#'*
371        exponent    := 'E' '0' '0'*
372      Notation:
373        X*       0 or more instances of X
374        { X }    0 or 1 instances of X
375        X | Y    either X or Y
376        X..Y     any character from X up to Y, inclusive
377        S - T    characters in S, except those in T
378      Single-quote starts a quoted section, to be terminated at the next
379      single-quote or string end.  Double single-quote gives a single
380      single-quote.
381    */
382   bool quoting = false;
383   bool seen_semicolon = false;
384 
385   HANDLE_QUOTE;
386   for (;;)
387     {
388       /* Parse prefix.  */
389       while (*format != '\0'
390 	     && !(!quoting && (*format == '0' || *format == '#')))
391 	{
392 	  if (format[0] == '\\')
393 	    {
394 	      if (format[1] == 'u'
395 		  && c_isxdigit (format[2])
396 		  && c_isxdigit (format[3])
397 		  && c_isxdigit (format[4])
398 		  && c_isxdigit (format[5]))
399 		format += 6;
400 	      else
401 		format += 2;
402 	    }
403 	  else
404 	    format += 1;
405 	  HANDLE_QUOTE;
406 	}
407 
408       /* Parse integer.  */
409       if (!(!quoting && (*format == '0' || *format == '#')))
410 	return false;
411       while (!quoting && *format == '#')
412 	{
413 	  format++;
414 	  HANDLE_QUOTE;
415 	  if (!quoting && *format == ',')
416 	    {
417 	      format++;
418 	      HANDLE_QUOTE;
419 	    }
420 	}
421       while (!quoting && *format == '0')
422 	{
423 	  format++;
424 	  HANDLE_QUOTE;
425 	  if (!quoting && *format == ',')
426 	    {
427 	      format++;
428 	      HANDLE_QUOTE;
429 	    }
430 	}
431 
432       /* Parse fraction.  */
433       if (!quoting && *format == '.')
434 	{
435 	  format++;
436 	  HANDLE_QUOTE;
437 	  while (!quoting && *format == '0')
438 	    {
439 	      format++;
440 	      HANDLE_QUOTE;
441 	    }
442 	  while (!quoting && *format == '#')
443 	    {
444 	      format++;
445 	      HANDLE_QUOTE;
446 	    }
447 	}
448 
449       /* Parse exponent.  */
450       if (!quoting && *format == 'E')
451 	{
452 	  const char *format_save = format;
453 	  format++;
454 	  HANDLE_QUOTE;
455 	  if (!quoting && *format == '0')
456 	    {
457 	      do
458 		{
459 		  format++;
460 		  HANDLE_QUOTE;
461 		}
462 	      while (!quoting && *format == '0');
463 	    }
464 	  else
465 	    {
466 	      /* Back up.  */
467 	      format = format_save;
468 	      quoting = false;
469 	    }
470 	}
471 
472       /* Parse suffix.  */
473       while (*format != '\0'
474 	     && (seen_semicolon || !(!quoting && *format == ';')))
475 	{
476 	  if (format[0] == '\\')
477 	    {
478 	      if (format[1] == 'u'
479 		  && c_isxdigit (format[2])
480 		  && c_isxdigit (format[3])
481 		  && c_isxdigit (format[4])
482 		  && c_isxdigit (format[5]))
483 		format += 6;
484 	      else
485 		format += 2;
486 	    }
487 	  else
488 	    format += 1;
489 	  HANDLE_QUOTE;
490 	}
491 
492       if (seen_semicolon || !(!quoting && *format == ';'))
493 	break;
494     }
495 
496   return (*format == '\0');
497 }
498 
499 /* Return true if a format is a valid choiceFormatPattern.
500    Extracts argument type information into spec.  */
501 static bool
choice_format_parse(const char * format,struct spec * spec,char ** invalid_reason)502 choice_format_parse (const char *format, struct spec *spec,
503 		     char **invalid_reason)
504 {
505   /* Pattern syntax:
506        pattern   := | choice | choice '|' pattern
507        choice    := number separator messageformat
508        separator := '<' | '#' | '\u2264'
509      Single-quote starts a quoted section, to be terminated at the next
510      single-quote or string end.  Double single-quote gives a single
511      single-quote.
512    */
513   bool quoting = false;
514 
515   HANDLE_QUOTE;
516   if (*format == '\0')
517     return true;
518   for (;;)
519     {
520       /* Don't bother looking too precisely into the syntax of the number.
521 	 It can contain various Unicode characters.  */
522       bool number_nonempty;
523       char *msgformat;
524       char *mp;
525       bool msgformat_valid;
526 
527       /* Parse number.  */
528       number_nonempty = false;
529       while (*format != '\0'
530 	     && !(!quoting && (*format == '<' || *format == '#'
531 			       || strncmp (format, "\\u2264", 6) == 0
532 			       || *format == '|')))
533 	{
534 	  if (format[0] == '\\')
535 	    {
536 	      if (format[1] == 'u'
537 		  && c_isxdigit (format[2])
538 		  && c_isxdigit (format[3])
539 		  && c_isxdigit (format[4])
540 		  && c_isxdigit (format[5]))
541 		format += 6;
542 	      else
543 		format += 2;
544 	    }
545 	  else
546 	    format += 1;
547 	  number_nonempty = true;
548 	  HANDLE_QUOTE;
549 	}
550 
551       /* Short clause at end of pattern is valid and is ignored!  */
552       if (*format == '\0')
553 	break;
554 
555       if (!number_nonempty)
556 	{
557 	  *invalid_reason =
558 	    xasprintf (_("In the directive number %u, a choice contains no number."), spec->directives);
559 	  return false;
560 	}
561 
562       if (*format == '<' || *format == '#')
563 	format += 1;
564       else if (strncmp (format, "\\u2264", 6) == 0)
565 	format += 6;
566       else
567 	{
568 	  *invalid_reason =
569 	    xasprintf (_("In the directive number %u, a choice contains a number that is not followed by '<', '#' or '%s'."), spec->directives, "\\u2264");
570 	  return false;
571 	}
572       HANDLE_QUOTE;
573 
574       msgformat = (char *) xallocsa (strlen (format) + 1);
575       mp = msgformat;
576 
577       while (*format != '\0' && !(!quoting && *format == '|'))
578 	{
579 	  *mp++ = *format++;
580 	  HANDLE_QUOTE;
581 	}
582       *mp = '\0';
583 
584       msgformat_valid = message_format_parse (msgformat, spec, invalid_reason);
585 
586       freesa (msgformat);
587 
588       if (!msgformat_valid)
589 	return false;
590 
591       if (*format == '\0')
592 	break;
593 
594       format++;
595       HANDLE_QUOTE;
596     }
597 
598   return true;
599 }
600 
601 static int
numbered_arg_compare(const void * p1,const void * p2)602 numbered_arg_compare (const void *p1, const void *p2)
603 {
604   unsigned int n1 = ((const struct numbered_arg *) p1)->number;
605   unsigned int n2 = ((const struct numbered_arg *) p2)->number;
606 
607   return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
608 }
609 
610 static void *
format_parse(const char * format,bool translated,char ** invalid_reason)611 format_parse (const char *format, bool translated, char **invalid_reason)
612 {
613   struct spec spec;
614   struct spec *result;
615 
616   spec.directives = 0;
617   spec.numbered_arg_count = 0;
618   spec.allocated = 0;
619   spec.numbered = NULL;
620 
621   if (!message_format_parse (format, &spec, invalid_reason))
622     goto bad_format;
623 
624   /* Sort the numbered argument array, and eliminate duplicates.  */
625   if (spec.numbered_arg_count > 1)
626     {
627       unsigned int i, j;
628       bool err;
629 
630       qsort (spec.numbered, spec.numbered_arg_count,
631 	     sizeof (struct numbered_arg), numbered_arg_compare);
632 
633       /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
634       err = false;
635       for (i = j = 0; i < spec.numbered_arg_count; i++)
636 	if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
637 	  {
638 	    enum format_arg_type type1 = spec.numbered[i].type;
639 	    enum format_arg_type type2 = spec.numbered[j-1].type;
640 	    enum format_arg_type type_both;
641 
642 	    if (type1 == type2 || type2 == FAT_OBJECT)
643 	      type_both = type1;
644 	    else if (type1 == FAT_OBJECT)
645 	      type_both = type2;
646 	    else
647 	      {
648 		/* Incompatible types.  */
649 		type_both = FAT_NONE;
650 		if (!err)
651 		  *invalid_reason =
652 		    INVALID_INCOMPATIBLE_ARG_TYPES (spec.numbered[i].number);
653 		err = true;
654 	      }
655 
656 	    spec.numbered[j-1].type = type_both;
657 	  }
658 	else
659 	  {
660 	    if (j < i)
661 	      {
662 		spec.numbered[j].number = spec.numbered[i].number;
663 		spec.numbered[j].type = spec.numbered[i].type;
664 	      }
665 	    j++;
666 	  }
667       spec.numbered_arg_count = j;
668       if (err)
669 	/* *invalid_reason has already been set above.  */
670 	goto bad_format;
671     }
672 
673   result = (struct spec *) xmalloc (sizeof (struct spec));
674   *result = spec;
675   return result;
676 
677  bad_format:
678   if (spec.numbered != NULL)
679     free (spec.numbered);
680   return NULL;
681 }
682 
683 static void
format_free(void * descr)684 format_free (void *descr)
685 {
686   struct spec *spec = (struct spec *) descr;
687 
688   if (spec->numbered != NULL)
689     free (spec->numbered);
690   free (spec);
691 }
692 
693 static int
format_get_number_of_directives(void * descr)694 format_get_number_of_directives (void *descr)
695 {
696   struct spec *spec = (struct spec *) descr;
697 
698   return spec->directives;
699 }
700 
701 static bool
format_check(void * msgid_descr,void * msgstr_descr,bool equality,formatstring_error_logger_t error_logger,const char * pretty_msgstr)702 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
703 	      formatstring_error_logger_t error_logger,
704 	      const char *pretty_msgstr)
705 {
706   struct spec *spec1 = (struct spec *) msgid_descr;
707   struct spec *spec2 = (struct spec *) msgstr_descr;
708   bool err = false;
709 
710   if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
711     {
712       unsigned int i, j;
713       unsigned int n1 = spec1->numbered_arg_count;
714       unsigned int n2 = spec2->numbered_arg_count;
715 
716       /* Check the argument names are the same.
717 	 Both arrays are sorted.  We search for the first difference.  */
718       for (i = 0, j = 0; i < n1 || j < n2; )
719 	{
720 	  int cmp = (i >= n1 ? 1 :
721 		     j >= n2 ? -1 :
722 		     spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
723 		     spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
724 		     0);
725 
726 	  if (cmp > 0)
727 	    {
728 	      if (error_logger)
729 		error_logger (_("a format specification for argument {%u}, as in '%s', doesn't exist in 'msgid'"),
730 			      spec2->numbered[j].number, pretty_msgstr);
731 	      err = true;
732 	      break;
733 	    }
734 	  else if (cmp < 0)
735 	    {
736 	      if (equality)
737 		{
738 		  if (error_logger)
739 		    error_logger (_("a format specification for argument {%u} doesn't exist in '%s'"),
740 				  spec1->numbered[i].number, pretty_msgstr);
741 		  err = true;
742 		  break;
743 		}
744 	      else
745 		i++;
746 	    }
747 	  else
748 	    j++, i++;
749 	}
750       /* Check the argument types are the same.  */
751       if (!err)
752 	for (i = 0, j = 0; j < n2; )
753 	  {
754 	    if (spec1->numbered[i].number == spec2->numbered[j].number)
755 	      {
756 		if (spec1->numbered[i].type != spec2->numbered[j].type)
757 		  {
758 		    if (error_logger)
759 		      error_logger (_("format specifications in 'msgid' and '%s' for argument {%u} are not the same"),
760 				    pretty_msgstr, spec2->numbered[j].number);
761 		    err = true;
762 		    break;
763 		  }
764 		j++, i++;
765 	      }
766 	    else
767 	      i++;
768 	  }
769     }
770 
771   return err;
772 }
773 
774 
775 struct formatstring_parser formatstring_java =
776 {
777   format_parse,
778   format_free,
779   format_get_number_of_directives,
780   NULL,
781   format_check
782 };
783 
784 
785 #ifdef TEST
786 
787 /* Test program: Print the argument list specification returned by
788    format_parse for strings read from standard input.  */
789 
790 #include <stdio.h>
791 #include "getline.h"
792 
793 static void
format_print(void * descr)794 format_print (void *descr)
795 {
796   struct spec *spec = (struct spec *) descr;
797   unsigned int last;
798   unsigned int i;
799 
800   if (spec == NULL)
801     {
802       printf ("INVALID");
803       return;
804     }
805 
806   printf ("(");
807   last = 0;
808   for (i = 0; i < spec->numbered_arg_count; i++)
809     {
810       unsigned int number = spec->numbered[i].number;
811 
812       if (i > 0)
813 	printf (" ");
814       if (number < last)
815 	abort ();
816       for (; last < number; last++)
817 	printf ("_ ");
818       switch (spec->numbered[i].type)
819 	{
820 	case FAT_OBJECT:
821 	  printf ("*");
822 	  break;
823 	case FAT_NUMBER:
824 	  printf ("Number");
825 	  break;
826 	case FAT_DATE:
827 	  printf ("Date");
828 	  break;
829 	default:
830 	  abort ();
831 	}
832       last = number + 1;
833     }
834   printf (")");
835 }
836 
837 int
main()838 main ()
839 {
840   for (;;)
841     {
842       char *line = NULL;
843       size_t line_size = 0;
844       int line_len;
845       char *invalid_reason;
846       void *descr;
847 
848       line_len = getline (&line, &line_size, stdin);
849       if (line_len < 0)
850 	break;
851       if (line_len > 0 && line[line_len - 1] == '\n')
852 	line[--line_len] = '\0';
853 
854       invalid_reason = NULL;
855       descr = format_parse (line, false, &invalid_reason);
856 
857       format_print (descr);
858       printf ("\n");
859       if (descr == NULL)
860 	printf ("%s\n", invalid_reason);
861 
862       free (invalid_reason);
863       free (line);
864     }
865 
866   return 0;
867 }
868 
869 /*
870  * For Emacs M-x compile
871  * Local Variables:
872  * compile-command: "/bin/sh ../libtool --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../lib -I../intl -DHAVE_CONFIG_H -DTEST format-java.c ../lib/libgettextlib.la"
873  * End:
874  */
875 
876 #endif /* TEST */
877