Coverage Report

Created: 2026-01-25 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gettext-0.26/gettext-tools/src/format-kde.c
Line
Count
Source
1
/* KDE format strings.
2
   Copyright (C) 2003-2025 Free Software Foundation, Inc.
3
   Written by Bruno Haible <bruno@clisp.org>, 2007.
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 3 of the License, or
8
   (at your option) 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, see <https://www.gnu.org/licenses/>.  */
17
18
#ifdef HAVE_CONFIG_H
19
# include <config.h>
20
#endif
21
22
#include <stdbool.h>
23
#include <stdlib.h>
24
25
#include "format.h"
26
#include "xalloc.h"
27
#include "xvasprintf.h"
28
#include "gettext.h"
29
30
0
#define _(str) gettext (str)
31
32
/* KDE 4 format strings are processed by method
33
   KLocalizedStringPrivate::substituteSimple(string,'%',false) in
34
   kde4libs-3.93.0.orig/kdecore/localization/klocalizedstring.cpp .
35
   A directive
36
     - starts with '%',
37
     - is followed by a non-zero digit and optionally more digits. All
38
       the following digits are eaten up.
39
   An unterminated directive ('%' not followed by a digit or at the end) is
40
   not an error.
41
   %1 denotes the first argument, %2 the second argument, etc.
42
   The set of used argument numbers must be of the form {1,...,n} or
43
   {1,...,n} \ {m}: one of the supplied arguments may be ignored by the
44
   format string. This allows the processing of singular forms (msgstr[0]).
45
   Which argument may be skipped, depends on the argument types at runtime;
46
   since xgettext cannot extract this info, it is considered unknown here.  */
47
48
struct numbered_arg
49
{
50
  size_t number;
51
};
52
53
struct spec
54
{
55
  size_t directives;
56
  size_t numbered_arg_count;
57
  struct numbered_arg *numbered;
58
};
59
60
static int
61
numbered_arg_compare (const void *p1, const void *p2)
62
0
{
63
  /* Subtract 1, because argument number 0 can only occur through overflow.  */
64
0
  size_t n1 = ((const struct numbered_arg *) p1)->number - 1;
65
0
  size_t n2 = ((const struct numbered_arg *) p2)->number - 1;
66
67
0
  return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
68
0
}
69
70
static void *
71
format_parse (const char *format, bool translated, char *fdi,
72
              char **invalid_reason)
73
0
{
74
0
  const char *const format_start = format;
75
0
  struct spec spec;
76
0
  size_t numbered_allocated;
77
0
  struct spec *result;
78
79
0
  spec.directives = 0;
80
0
  spec.numbered_arg_count = 0;
81
0
  spec.numbered = NULL;
82
0
  numbered_allocated = 0;
83
84
0
  for (; *format != '\0';)
85
0
    if (*format++ == '%')
86
0
      {
87
0
        const char *dir_start = format - 1;
88
89
0
        if (*format > '0' && *format <= '9')
90
0
          {
91
            /* A directive.  */
92
0
            size_t number;
93
94
0
            FDI_SET (dir_start, FMTDIR_START);
95
0
            spec.directives++;
96
97
0
            number = *format - '0';
98
0
            while (format[1] >= '0' && format[1] <= '9')
99
0
              {
100
0
                number = 10 * number + (format[1] - '0');
101
0
                format++;
102
0
              }
103
104
0
            if (numbered_allocated == spec.numbered_arg_count)
105
0
              {
106
0
                numbered_allocated = 2 * numbered_allocated + 1;
107
0
                spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
108
0
              }
109
0
            spec.numbered[spec.numbered_arg_count].number = number;
110
0
            spec.numbered_arg_count++;
111
112
0
            FDI_SET (format, FMTDIR_END);
113
114
0
            format++;
115
0
          }
116
0
      }
117
118
  /* Sort the numbered argument array, and eliminate duplicates.  */
119
0
  if (spec.numbered_arg_count > 1)
120
0
    {
121
0
      size_t i, j;
122
123
0
      qsort (spec.numbered, spec.numbered_arg_count,
124
0
             sizeof (struct numbered_arg), numbered_arg_compare);
125
126
      /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
127
0
      for (i = j = 0; i < spec.numbered_arg_count; i++)
128
0
        if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
129
0
          ;
130
0
        else
131
0
          {
132
0
            if (j < i)
133
0
              spec.numbered[j].number = spec.numbered[i].number;
134
0
            j++;
135
0
          }
136
0
      spec.numbered_arg_count = j;
137
0
    }
138
  /* Now spec.numbered[i] >= i + 1 for i = 0,..,spec.numbered_arg_count-1
139
     (since the numbered argument counts are strictly increasing, considering
140
     0 as overflow).  */
141
142
  /* Verify that the argument numbers are of the form {1,...,n} or
143
     {1,...,n} \ {m}.  */
144
0
  if (spec.numbered_arg_count > 0)
145
0
    {
146
0
      size_t i;
147
148
0
      i = 0;
149
0
      for (; i < spec.numbered_arg_count; i++)
150
0
        if (spec.numbered[i].number > i + 1)
151
0
          {
152
0
            size_t first_gap = i + 1;
153
0
            for (; i < spec.numbered_arg_count; i++)
154
0
              if (spec.numbered[i].number > i + 2)
155
0
                {
156
0
                  size_t second_gap = i + 2;
157
0
                  *invalid_reason =
158
0
                    xasprintf (_("The string refers to argument number %zu but ignores the arguments %zu and %zu."),
159
0
                               spec.numbered[i].number, first_gap, second_gap);
160
0
                  goto bad_format;
161
0
                }
162
0
             break;
163
0
          }
164
0
    }
165
166
0
  result = XMALLOC (struct spec);
167
0
  *result = spec;
168
0
  return result;
169
170
0
 bad_format:
171
0
  if (spec.numbered != NULL)
172
0
    free (spec.numbered);
173
0
  return NULL;
174
0
}
175
176
static void
177
format_free (void *descr)
178
0
{
179
0
  struct spec *spec = (struct spec *) descr;
180
181
0
  if (spec->numbered != NULL)
182
0
    free (spec->numbered);
183
0
  free (spec);
184
0
}
185
186
static int
187
format_get_number_of_directives (void *descr)
188
0
{
189
0
  struct spec *spec = (struct spec *) descr;
190
191
0
  return spec->directives;
192
0
}
193
194
static bool
195
format_check (void *msgid_descr, void *msgstr_descr, bool equality,
196
              formatstring_error_logger_t error_logger, void *error_logger_data,
197
              const char *pretty_msgid, const char *pretty_msgstr)
198
0
{
199
0
  struct spec *spec1 = (struct spec *) msgid_descr;
200
0
  struct spec *spec2 = (struct spec *) msgstr_descr;
201
0
  bool err = false;
202
203
0
  if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
204
0
    {
205
0
      size_t i, j;
206
0
      size_t n1 = spec1->numbered_arg_count;
207
0
      size_t n2 = spec2->numbered_arg_count;
208
0
      size_t missing = 0; /* only used if !equality */
209
210
      /* Check that the argument numbers are the same.
211
         Both arrays are sorted.  We search for the first difference.  */
212
0
      for (i = 0, j = 0; i < n1 || j < n2; )
213
0
        {
214
0
          int cmp = (i >= n1 ? 1 :
215
0
                     j >= n2 ? -1 :
216
0
                     spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
217
0
                     spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
218
0
                     0);
219
220
0
          if (cmp > 0)
221
0
            {
222
0
              if (error_logger)
223
0
                error_logger (error_logger_data,
224
0
                              _("a format specification for argument %zu, as in '%s', doesn't exist in '%s'"),
225
0
                              spec2->numbered[j].number, pretty_msgstr,
226
0
                              pretty_msgid);
227
0
              err = true;
228
0
              break;
229
0
            }
230
0
          else if (cmp < 0)
231
0
            {
232
0
              if (equality)
233
0
                {
234
0
                  if (error_logger)
235
0
                    error_logger (error_logger_data,
236
0
                                  _("a format specification for argument %zu doesn't exist in '%s'"),
237
0
                                  spec1->numbered[i].number, pretty_msgstr);
238
0
                  err = true;
239
0
                  break;
240
0
                }
241
0
              else if (missing)
242
0
                {
243
0
                  if (error_logger)
244
0
                    error_logger (error_logger_data,
245
0
                                  _("a format specification for arguments %zu and %zu doesn't exist in '%s', only one argument may be ignored"),
246
0
                                  missing, spec1->numbered[i].number,
247
0
                                  pretty_msgstr);
248
0
                  err = true;
249
0
                  break;
250
0
                }
251
0
              else
252
0
                {
253
0
                  missing = spec1->numbered[i].number;
254
0
                  i++;
255
0
                }
256
0
            }
257
0
          else
258
0
            j++, i++;
259
0
        }
260
0
    }
261
262
0
  return err;
263
0
}
264
265
266
struct formatstring_parser formatstring_kde =
267
{
268
  format_parse,
269
  format_free,
270
  format_get_number_of_directives,
271
  NULL,
272
  format_check
273
};
274
275
276
#ifdef TEST
277
278
/* Test program: Print the argument list specification returned by
279
   format_parse for strings read from standard input.  */
280
281
#include <stdio.h>
282
283
static void
284
format_print (void *descr)
285
{
286
  struct spec *spec = (struct spec *) descr;
287
  size_t last;
288
  size_t i;
289
290
  if (spec == NULL)
291
    {
292
      printf ("INVALID");
293
      return;
294
    }
295
296
  printf ("(");
297
  last = 1;
298
  for (i = 0; i < spec->numbered_arg_count; i++)
299
    {
300
      size_t number = spec->numbered[i].number;
301
302
      if (i > 0)
303
        printf (" ");
304
      if (number < last)
305
        abort ();
306
      for (; last < number; last++)
307
        printf ("_ ");
308
      printf ("*");
309
      last = number + 1;
310
    }
311
  printf (")");
312
}
313
314
int
315
main ()
316
{
317
  for (;;)
318
    {
319
      char *line = NULL;
320
      size_t line_size = 0;
321
      int line_len;
322
      char *invalid_reason;
323
      void *descr;
324
325
      line_len = getline (&line, &line_size, stdin);
326
      if (line_len < 0)
327
        break;
328
      if (line_len > 0 && line[line_len - 1] == '\n')
329
        line[--line_len] = '\0';
330
331
      invalid_reason = NULL;
332
      descr = format_parse (line, false, NULL, &invalid_reason);
333
334
      format_print (descr);
335
      printf ("\n");
336
      if (descr == NULL)
337
        printf ("%s\n", invalid_reason);
338
339
      free (invalid_reason);
340
      free (line);
341
    }
342
343
  return 0;
344
}
345
346
/*
347
 * For Emacs M-x compile
348
 * Local Variables:
349
 * 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 -DHAVE_CONFIG_H -DTEST format-kde.c ../gnulib-lib/libgettextlib.la"
350
 * End:
351
 */
352
353
#endif /* TEST */