• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* KDE format strings.
2    Copyright (C) 2003-2004, 2006-2007, 2009, 2019-2020 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 #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   unsigned int number;
51 };
52 
53 struct spec
54 {
55   unsigned int directives;
56   unsigned int numbered_arg_count;
57   struct numbered_arg *numbered;
58 };
59 
60 static int
numbered_arg_compare(const void * p1,const void * p2)61 numbered_arg_compare (const void *p1, const void *p2)
62 {
63   /* Subtract 1, because argument number 0 can only occur through overflow.  */
64   unsigned int n1 = ((const struct numbered_arg *) p1)->number - 1;
65   unsigned int n2 = ((const struct numbered_arg *) p2)->number - 1;
66 
67   return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
68 }
69 
70 static void *
format_parse(const char * format,bool translated,char * fdi,char ** invalid_reason)71 format_parse (const char *format, bool translated, char *fdi,
72               char **invalid_reason)
73 {
74   const char *const format_start = format;
75   struct spec spec;
76   unsigned int numbered_allocated;
77   struct spec *result;
78 
79   spec.directives = 0;
80   spec.numbered_arg_count = 0;
81   spec.numbered = NULL;
82   numbered_allocated = 0;
83 
84   for (; *format != '\0';)
85     if (*format++ == '%')
86       {
87         const char *dir_start = format - 1;
88 
89         if (*format > '0' && *format <= '9')
90           {
91             /* A directive.  */
92             unsigned int number;
93 
94             FDI_SET (dir_start, FMTDIR_START);
95             spec.directives++;
96 
97             number = *format - '0';
98             while (format[1] >= '0' && format[1] <= '9')
99               {
100                 number = 10 * number + (format[1] - '0');
101                 format++;
102               }
103 
104             if (numbered_allocated == spec.numbered_arg_count)
105               {
106                 numbered_allocated = 2 * numbered_allocated + 1;
107                 spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
108               }
109             spec.numbered[spec.numbered_arg_count].number = number;
110             spec.numbered_arg_count++;
111 
112             FDI_SET (format, FMTDIR_END);
113 
114             format++;
115           }
116       }
117 
118   /* Sort the numbered argument array, and eliminate duplicates.  */
119   if (spec.numbered_arg_count > 1)
120     {
121       unsigned int i, j;
122 
123       qsort (spec.numbered, spec.numbered_arg_count,
124              sizeof (struct numbered_arg), numbered_arg_compare);
125 
126       /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
127       for (i = j = 0; i < spec.numbered_arg_count; i++)
128         if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
129           ;
130         else
131           {
132             if (j < i)
133               spec.numbered[j].number = spec.numbered[i].number;
134             j++;
135           }
136       spec.numbered_arg_count = j;
137     }
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   if (spec.numbered_arg_count > 0)
145     {
146       unsigned int i;
147 
148       i = 0;
149       for (; i < spec.numbered_arg_count; i++)
150         if (spec.numbered[i].number > i + 1)
151           {
152             unsigned int first_gap = i + 1;
153             for (; i < spec.numbered_arg_count; i++)
154               if (spec.numbered[i].number > i + 2)
155                 {
156                   unsigned int second_gap = i + 2;
157                   *invalid_reason =
158                     xasprintf (_("The string refers to argument number %u but ignores the arguments %u and %u."),
159                                spec.numbered[i].number, first_gap, second_gap);
160                   goto bad_format;
161                 }
162              break;
163           }
164     }
165 
166   result = XMALLOC (struct spec);
167   *result = spec;
168   return result;
169 
170  bad_format:
171   if (spec.numbered != NULL)
172     free (spec.numbered);
173   return NULL;
174 }
175 
176 static void
format_free(void * descr)177 format_free (void *descr)
178 {
179   struct spec *spec = (struct spec *) descr;
180 
181   if (spec->numbered != NULL)
182     free (spec->numbered);
183   free (spec);
184 }
185 
186 static int
format_get_number_of_directives(void * descr)187 format_get_number_of_directives (void *descr)
188 {
189   struct spec *spec = (struct spec *) descr;
190 
191   return spec->directives;
192 }
193 
194 static bool
format_check(void * msgid_descr,void * msgstr_descr,bool equality,formatstring_error_logger_t error_logger,const char * pretty_msgid,const char * pretty_msgstr)195 format_check (void *msgid_descr, void *msgstr_descr, bool equality,
196               formatstring_error_logger_t error_logger,
197               const char *pretty_msgid, const char *pretty_msgstr)
198 {
199   struct spec *spec1 = (struct spec *) msgid_descr;
200   struct spec *spec2 = (struct spec *) msgstr_descr;
201   bool err = false;
202 
203   if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
204     {
205       unsigned int i, j;
206       unsigned int n1 = spec1->numbered_arg_count;
207       unsigned int n2 = spec2->numbered_arg_count;
208       unsigned int missing = 0; /* only used if !equality */
209 
210       /* Check the argument names are the same.
211          Both arrays are sorted.  We search for the first difference.  */
212       for (i = 0, j = 0; i < n1 || j < n2; )
213         {
214           int cmp = (i >= n1 ? 1 :
215                      j >= n2 ? -1 :
216                      spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
217                      spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
218                      0);
219 
220           if (cmp > 0)
221             {
222               if (error_logger)
223                 error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
224                               spec2->numbered[j].number, pretty_msgstr,
225                               pretty_msgid);
226               err = true;
227               break;
228             }
229           else if (cmp < 0)
230             {
231               if (equality)
232                 {
233                   if (error_logger)
234                     error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
235                                   spec1->numbered[i].number, pretty_msgstr);
236                   err = true;
237                   break;
238                 }
239               else if (missing)
240                 {
241                   if (error_logger)
242                     error_logger (_("a format specification for arguments %u and %u doesn't exist in '%s', only one argument may be ignored"),
243                                   missing, spec1->numbered[i].number,
244                                   pretty_msgstr);
245                   err = true;
246                   break;
247                 }
248               else
249                 {
250                   missing = spec1->numbered[i].number;
251                   i++;
252                 }
253             }
254           else
255             j++, i++;
256         }
257     }
258 
259   return err;
260 }
261 
262 
263 struct formatstring_parser formatstring_kde =
264 {
265   format_parse,
266   format_free,
267   format_get_number_of_directives,
268   NULL,
269   format_check
270 };
271 
272 
273 #ifdef TEST
274 
275 /* Test program: Print the argument list specification returned by
276    format_parse for strings read from standard input.  */
277 
278 #include <stdio.h>
279 
280 static void
format_print(void * descr)281 format_print (void *descr)
282 {
283   struct spec *spec = (struct spec *) descr;
284   unsigned int last;
285   unsigned int i;
286 
287   if (spec == NULL)
288     {
289       printf ("INVALID");
290       return;
291     }
292 
293   printf ("(");
294   last = 1;
295   for (i = 0; i < spec->numbered_arg_count; i++)
296     {
297       unsigned int number = spec->numbered[i].number;
298 
299       if (i > 0)
300         printf (" ");
301       if (number < last)
302         abort ();
303       for (; last < number; last++)
304         printf ("_ ");
305       last = number + 1;
306     }
307   printf (")");
308 }
309 
310 int
main()311 main ()
312 {
313   for (;;)
314     {
315       char *line = NULL;
316       size_t line_size = 0;
317       int line_len;
318       char *invalid_reason;
319       void *descr;
320 
321       line_len = getline (&line, &line_size, stdin);
322       if (line_len < 0)
323         break;
324       if (line_len > 0 && line[line_len - 1] == '\n')
325         line[--line_len] = '\0';
326 
327       invalid_reason = NULL;
328       descr = format_parse (line, false, NULL, &invalid_reason);
329 
330       format_print (descr);
331       printf ("\n");
332       if (descr == NULL)
333         printf ("%s\n", invalid_reason);
334 
335       free (invalid_reason);
336       free (line);
337     }
338 
339   return 0;
340 }
341 
342 /*
343  * For Emacs M-x compile
344  * Local Variables:
345  * 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"
346  * End:
347  */
348 
349 #endif /* TEST */
350