1/* KDE format strings. 2 Copyright (C) 2003-2004, 2006-2007 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 <http://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 48struct numbered_arg 49{ 50 unsigned int number; 51}; 52 53struct spec 54{ 55 unsigned int directives; 56 unsigned int numbered_arg_count; 57 unsigned int allocated; 58 struct numbered_arg *numbered; 59}; 60 61static int 62numbered_arg_compare (const void *p1, const void *p2) 63{ 64 /* Subtract 1, because argument number 0 can only occur through overflow. */ 65 unsigned int n1 = ((const struct numbered_arg *) p1)->number - 1; 66 unsigned int n2 = ((const struct numbered_arg *) p2)->number - 1; 67 68 return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0); 69} 70 71static void * 72format_parse (const char *format, bool translated, char *fdi, 73 char **invalid_reason) 74{ 75 const char *const format_start = format; 76 struct spec spec; 77 struct spec *result; 78 79 spec.directives = 0; 80 spec.numbered_arg_count = 0; 81 spec.allocated = 0; 82 spec.numbered = NULL; 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 (spec.allocated == spec.numbered_arg_count) 105 { 106 spec.allocated = 2 * spec.allocated + 1; 107 spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, spec.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 176static void 177format_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 186static int 187format_get_number_of_directives (void *descr) 188{ 189 struct spec *spec = (struct spec *) descr; 190 191 return spec->directives; 192} 193 194static bool 195format_check (void *msgid_descr, void *msgstr_descr, bool equality, 196 formatstring_error_logger_t error_logger, 197 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 'msgid'"), 224 spec2->numbered[j].number, pretty_msgstr); 225 err = true; 226 break; 227 } 228 else if (cmp < 0) 229 { 230 if (equality) 231 { 232 if (error_logger) 233 error_logger (_("a format specification for argument %u doesn't exist in '%s'"), 234 spec1->numbered[i].number, pretty_msgstr); 235 err = true; 236 break; 237 } 238 else if (missing) 239 { 240 if (error_logger) 241 error_logger (_("a format specification for arguments %u and %u doesn't exist in '%s', only one argument may be ignored"), 242 missing, spec1->numbered[i].number, pretty_msgstr); 243 err = true; 244 break; 245 } 246 else 247 { 248 missing = spec1->numbered[i].number; 249 i++; 250 } 251 } 252 else 253 j++, i++; 254 } 255 } 256 257 return err; 258} 259 260 261struct formatstring_parser formatstring_kde = 262{ 263 format_parse, 264 format_free, 265 format_get_number_of_directives, 266 NULL, 267 format_check 268}; 269 270 271#ifdef TEST 272 273/* Test program: Print the argument list specification returned by 274 format_parse for strings read from standard input. */ 275 276#include <stdio.h> 277 278static void 279format_print (void *descr) 280{ 281 struct spec *spec = (struct spec *) descr; 282 unsigned int last; 283 unsigned int i; 284 285 if (spec == NULL) 286 { 287 printf ("INVALID"); 288 return; 289 } 290 291 printf ("("); 292 last = 1; 293 for (i = 0; i < spec->numbered_arg_count; i++) 294 { 295 unsigned int number = spec->numbered[i].number; 296 297 if (i > 0) 298 printf (" "); 299 if (number < last) 300 abort (); 301 for (; last < number; last++) 302 printf ("_ "); 303 last = number + 1; 304 } 305 printf (")"); 306} 307 308int 309main () 310{ 311 for (;;) 312 { 313 char *line = NULL; 314 size_t line_size = 0; 315 int line_len; 316 char *invalid_reason; 317 void *descr; 318 319 line_len = getline (&line, &line_size, stdin); 320 if (line_len < 0) 321 break; 322 if (line_len > 0 && line[line_len - 1] == '\n') 323 line[--line_len] = '\0'; 324 325 invalid_reason = NULL; 326 descr = format_parse (line, false, NULL, &invalid_reason); 327 328 format_print (descr); 329 printf ("\n"); 330 if (descr == NULL) 331 printf ("%s\n", invalid_reason); 332 333 free (invalid_reason); 334 free (line); 335 } 336 337 return 0; 338} 339 340/* 341 * For Emacs M-x compile 342 * Local Variables: 343 * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../intl -DHAVE_CONFIG_H -DTEST format-kde.c ../gnulib-lib/libgettextlib.la" 344 * End: 345 */ 346 347#endif /* TEST */ 348