Coverage Report

Created: 2026-02-26 07:48

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