Home | History | Annotate | Line # | Download | only in src
      1 /* Java format strings.
      2    Copyright (C) 2001-2004, 2006 Free Software Foundation, Inc.
      3    Written by Bruno Haible <haible (at) 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
    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
    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
    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
    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
    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 *
    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
    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
    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
    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
    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
    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