Coverage Report

Created: 2026-10-28 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gettext/gettext-tools/src/format-qt.c
Line
Count
Source
1
/* Qt format strings.
2
   Copyright (C) 2003-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
21
#include <stdbool.h>
22
#include <stdlib.h>
23
24
#include "format.h"
25
#include "attribute.h"
26
#include "xalloc.h"
27
#include "xvasprintf.h"
28
#include "gettext.h"
29
30
0
#define _(str) gettext (str)
31
32
/* Qt format strings are processed by QString::arg and are documented in
33
   qt-4.3.0/doc/html/qstring.html.
34
   A directive
35
     - starts with '%',
36
     - is optionally followed by 'L' (indicates locale-dependent processing),
37
     - is followed by one or two digits ('0' to '9'). %0n is equivalent to %n.
38
   An unterminated directive ('%' or '%L' not followed by a digit or at the
39
   end) is not an error.
40
   The first .arg() invocation replaces the %n with the lowest numbered n,
41
   the next .arg() invocation then replaces the %n with the second-lowest
42
   numbered n, and so on.
43
   This is inherently buggy because a '%' in the first replacement confuses
44
   the second .arg() invocation.
45
   To reduce this problem and introduce another one, there are also .arg()
46
   methods that take up to 9 strings and perform the replacements in one swoop.
47
   But this method works only on strings that contain no 'L' flags and only
48
   single-digit argument designators.
49
   Although %0 is supported, usually %1 denotes the first argument, %2 the
50
   second argument etc.  */
51
52
struct spec
53
{
54
  /* Number of format directives.  */
55
  size_t directives;
56
57
  /* True if the string supports the multi-argument .arg() methods, i.e. if it
58
     contains no 'L' flags and only single-digit argument designators.  */
59
  bool simple;
60
61
  /* Booleans telling which %nn was seen.  */
62
  size_t arg_count;
63
  bool args_used[100]
64
    /* COUNTED_BY (arg_count) */;
65
};
66
67
68
static void *
69
format_parse (const char *format, bool translated, char *fdi,
70
              char **invalid_reason)
71
0
{
72
0
  const char *const format_start = format;
73
74
0
  struct spec spec;
75
0
  spec.directives = 0;
76
0
  spec.simple = true;
77
0
  spec.arg_count = 0;
78
79
0
  for (; *format != '\0';)
80
0
    if (*format++ == '%')
81
0
      {
82
0
        const char *dir_start = format - 1;
83
0
        bool locale_flag = false;
84
85
0
        if (*format == 'L')
86
0
          {
87
0
            locale_flag = true;
88
0
            format++;
89
0
          }
90
0
        if (*format >= '0' && *format <= '9')
91
0
          {
92
            /* A directive.  */
93
0
            FDI_SET (dir_start, FMTDIR_START);
94
0
            spec.directives++;
95
0
            if (locale_flag)
96
0
              spec.simple = false;
97
98
0
            size_t number = *format - '0';
99
0
            if (format[1] >= '0' && format[1] <= '9')
100
0
              {
101
0
                number = 10 * number + (format[1] - '0');
102
0
                spec.simple = false;
103
0
                format++;
104
0
              }
105
106
0
            while (spec.arg_count <= number)
107
0
              spec.args_used[spec.arg_count++] = false;
108
0
            spec.args_used[number] = true;
109
110
0
            FDI_SET (format, FMTDIR_END);
111
112
0
            format++;
113
0
          }
114
0
      }
115
116
0
  struct spec *result = XMALLOC (struct spec);
117
0
  *result = spec;
118
0
  return result;
119
0
}
120
121
static void
122
format_free (void *descr)
123
0
{
124
0
  struct spec *spec = (struct spec *) descr;
125
126
0
  free (spec);
127
0
}
128
129
static int
130
format_get_number_of_directives (void *descr)
131
0
{
132
0
  struct spec *spec = (struct spec *) descr;
133
134
0
  return spec->directives;
135
0
}
136
137
static bool
138
format_check (void *msgid_descr, void *msgstr_descr, bool equality,
139
              formatstring_error_logger_t error_logger, void *error_logger_data,
140
              const char *pretty_msgid, const char *pretty_msgstr)
141
0
{
142
0
  struct spec *spec1 = (struct spec *) msgid_descr;
143
0
  struct spec *spec2 = (struct spec *) msgstr_descr;
144
0
  bool err = false;
145
146
0
  if (spec1->simple && !spec2->simple)
147
0
    {
148
0
      if (error_logger)
149
0
        error_logger (error_logger_data,
150
0
                      _("'%s' is a simple format string, but '%s' is not: it contains an 'L' flag or a double-digit argument number"),
151
0
                      pretty_msgid, pretty_msgstr);
152
0
      err = true;
153
0
    }
154
155
0
  if (!err)
156
0
    for (size_t i = 0; i < spec1->arg_count || i < spec2->arg_count; i++)
157
0
      {
158
0
        bool arg_used1 = (i < spec1->arg_count && spec1->args_used[i]);
159
0
        bool arg_used2 = (i < spec2->arg_count && spec2->args_used[i]);
160
161
        /* The translator cannot omit a %n from the msgstr because that would
162
           yield a "Argument missing" warning at runtime.  */
163
0
        if (arg_used1 != arg_used2)
164
0
          {
165
0
            if (error_logger)
166
0
              {
167
0
                if (arg_used1)
168
0
                  error_logger (error_logger_data,
169
0
                                _("a format specification for argument %zu doesn't exist in '%s'"),
170
0
                                i, pretty_msgstr);
171
0
                else
172
0
                  error_logger (error_logger_data,
173
0
                                _("a format specification for argument %zu, as in '%s', doesn't exist in '%s'"),
174
0
                                i, pretty_msgstr, pretty_msgid);
175
0
              }
176
0
            err = true;
177
0
            break;
178
0
          }
179
0
      }
180
181
0
  return err;
182
0
}
183
184
185
struct formatstring_parser formatstring_qt =
186
{
187
  format_parse,
188
  format_free,
189
  format_get_number_of_directives,
190
  NULL,
191
  format_check
192
};
193
194
195
#ifdef TEST
196
197
/* Test program: Print the argument list specification returned by
198
   format_parse for strings read from standard input.  */
199
200
#include <stdio.h>
201
202
static void
203
format_print (void *descr)
204
{
205
  struct spec *spec = (struct spec *) descr;
206
207
  if (spec == NULL)
208
    {
209
      printf ("INVALID");
210
      return;
211
    }
212
213
  printf ("(");
214
  for (size_t i = 0; i < spec->arg_count; i++)
215
    {
216
      if (i > 0)
217
        printf (" ");
218
      if (spec->args_used[i])
219
        printf ("*");
220
      else
221
        printf ("_");
222
    }
223
  printf (")");
224
}
225
226
int
227
main ()
228
{
229
  for (;;)
230
    {
231
      char *line = NULL;
232
      size_t line_size = 0;
233
      int line_len = getline (&line, &line_size, stdin);
234
      if (line_len < 0)
235
        break;
236
      if (line_len > 0 && line[line_len - 1] == '\n')
237
        line[--line_len] = '\0';
238
239
      char *invalid_reason = NULL;
240
      void *descr = format_parse (line, false, NULL, &invalid_reason);
241
242
      format_print (descr);
243
      printf ("\n");
244
      if (descr == NULL)
245
        printf ("%s\n", invalid_reason);
246
247
      free (invalid_reason);
248
      free (line);
249
    }
250
251
  return 0;
252
}
253
254
/*
255
 * For Emacs M-x compile
256
 * Local Variables:
257
 * 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-qt.c ../gnulib-lib/libgettextlib.la"
258
 * End:
259
 */
260
261
#endif /* TEST */