]>
Commit | Line | Data |
---|---|---|
c277e2f8 | 1 | /* |
75bd9771 | 2 | * "$Id: checkpo.c 7223 2008-01-16 23:41:19Z mike $" |
c277e2f8 | 3 | * |
71e16022 MS |
4 | * Verify that translations in the .po file have the same number and type of |
5 | * printf-style format strings. | |
c277e2f8 | 6 | * |
5a9febac | 7 | * Copyright 2007-2012 by Apple Inc. |
71e16022 | 8 | * Copyright 1997-2007 by Easy Software Products, all rights reserved. |
f0ab5bff | 9 | * |
71e16022 MS |
10 | * These coded instructions, statements, and computer programs are the |
11 | * property of Apple Inc. and are protected by Federal copyright | |
12 | * law. Distribution and use rights are outlined in the file "LICENSE.txt" | |
13 | * which should have been included with this file. If this file is | |
14 | * file is missing or damaged, see the license at "http://www.cups.org/". | |
f0ab5bff | 15 | * |
c277e2f8 MS |
16 | * Usage: |
17 | * | |
18 | * checkpo filename.po [... filenameN.po] | |
19 | * | |
20 | * Compile with: | |
21 | * | |
22 | * gcc -o checkpo checkpo.c `cups-config --libs` | |
23 | * | |
24 | * Contents: | |
25 | * | |
26 | * main() - Validate .po files. | |
27 | * abbreviate() - Abbreviate a message string as needed. | |
28 | * collect_formats() - Collect all of the format strings in the msgid. | |
29 | * free_formats() - Free all of the format strings. | |
30 | */ | |
31 | ||
71e16022 | 32 | #include <cups/cups-private.h> |
c277e2f8 MS |
33 | |
34 | ||
35 | /* | |
36 | * Local functions... | |
37 | */ | |
38 | ||
39 | static char *abbreviate(const char *s, char *buf, int bufsize); | |
40 | static cups_array_t *collect_formats(const char *id); | |
41 | static void free_formats(cups_array_t *fmts); | |
42 | ||
43 | ||
44 | /* | |
45 | * 'main()' - Validate .po files. | |
46 | */ | |
47 | ||
48 | int /* O - Exit code */ | |
49 | main(int argc, /* I - Number of command-line args */ | |
50 | char *argv[]) /* I - Command-line arguments */ | |
51 | { | |
52 | int i; /* Looping var */ | |
53 | cups_array_t *po; /* .po file */ | |
54 | _cups_message_t *msg; /* Current message */ | |
55 | cups_array_t *idfmts, /* Format strings in msgid */ | |
56 | *strfmts; /* Format strings in msgstr */ | |
57 | char *idfmt, /* Current msgid format string */ | |
58 | *strfmt; /* Current msgstr format string */ | |
91c84a35 | 59 | int fmtidx; /* Format index */ |
c277e2f8 MS |
60 | int status, /* Exit status */ |
61 | pass, /* Pass/fail status */ | |
62 | untranslated; /* Untranslated messages */ | |
63 | char idbuf[80], /* Abbreviated msgid */ | |
64 | strbuf[80]; /* Abbreviated msgstr */ | |
65 | ||
66 | ||
67 | if (argc < 2) | |
68 | { | |
69 | puts("Usage: checkpo filename.po [... filenameN.po]"); | |
70 | return (1); | |
71 | } | |
72 | ||
73 | /* | |
74 | * Check every .po file on the command-line... | |
75 | */ | |
76 | ||
77 | for (i = 1, status = 0; i < argc; i ++) | |
78 | { | |
79 | /* | |
80 | * Use the CUPS .po loader to get the message strings... | |
81 | */ | |
82 | ||
0837b7e8 | 83 | if ((po = _cupsMessageLoad(argv[i], 1)) == NULL) |
c277e2f8 MS |
84 | { |
85 | perror(argv[i]); | |
86 | return (1); | |
87 | } | |
88 | ||
0837b7e8 MS |
89 | if (i > 1) |
90 | putchar('\n'); | |
c277e2f8 MS |
91 | printf("%s: ", argv[i]); |
92 | fflush(stdout); | |
93 | ||
94 | /* | |
95 | * Scan every message for a % string and then match them up with | |
96 | * the corresponding string in the translation... | |
97 | */ | |
98 | ||
99 | pass = 1; | |
100 | untranslated = 0; | |
101 | ||
102 | for (msg = (_cups_message_t *)cupsArrayFirst(po); | |
103 | msg; | |
104 | msg = (_cups_message_t *)cupsArrayNext(po)) | |
105 | { | |
0837b7e8 MS |
106 | /* |
107 | * Make sure filter message prefixes are not translated... | |
108 | */ | |
109 | ||
110 | if (!strncmp(msg->id, "ALERT:", 6) || !strncmp(msg->id, "CRIT:", 5) || | |
111 | !strncmp(msg->id, "DEBUG:", 6) || !strncmp(msg->id, "DEBUG2:", 7) || | |
112 | !strncmp(msg->id, "EMERG:", 6) || !strncmp(msg->id, "ERROR:", 6) || | |
113 | !strncmp(msg->id, "INFO:", 5) || !strncmp(msg->id, "NOTICE:", 7) || | |
114 | !strncmp(msg->id, "WARNING:", 8)) | |
115 | { | |
116 | if (pass) | |
117 | { | |
118 | pass = 0; | |
119 | puts("FAIL"); | |
120 | } | |
121 | ||
122 | printf(" Bad prefix on filter message \"%s\"\n", | |
123 | abbreviate(msg->id, idbuf, sizeof(idbuf))); | |
124 | } | |
125 | ||
126 | idfmt = msg->id + strlen(msg->id) - 1; | |
127 | if (idfmt >= msg->id && *idfmt == '\n') | |
128 | { | |
129 | if (pass) | |
130 | { | |
131 | pass = 0; | |
132 | puts("FAIL"); | |
133 | } | |
134 | ||
135 | printf(" Trailing newline in message \"%s\"\n", | |
136 | abbreviate(msg->id, idbuf, sizeof(idbuf))); | |
137 | } | |
138 | ||
139 | for (; idfmt >= msg->id; idfmt --) | |
140 | if (!isspace(*idfmt & 255)) | |
141 | break; | |
142 | ||
143 | if (idfmt >= msg->id && *idfmt == '!') | |
144 | { | |
145 | if (pass) | |
146 | { | |
147 | pass = 0; | |
148 | puts("FAIL"); | |
149 | } | |
150 | ||
151 | printf(" Exclamation in message \"%s\"\n", | |
152 | abbreviate(msg->id, idbuf, sizeof(idbuf))); | |
153 | } | |
154 | ||
155 | if ((idfmt - 2) >= msg->id && !strncmp(idfmt - 2, "...", 3)) | |
156 | { | |
157 | if (pass) | |
158 | { | |
159 | pass = 0; | |
160 | puts("FAIL"); | |
161 | } | |
162 | ||
163 | printf(" Ellipsis in message \"%s\"\n", | |
164 | abbreviate(msg->id, idbuf, sizeof(idbuf))); | |
165 | } | |
166 | ||
167 | ||
c277e2f8 MS |
168 | if (!msg->str || !msg->str[0]) |
169 | { | |
170 | untranslated ++; | |
171 | continue; | |
172 | } | |
173 | else if (strchr(msg->id, '%')) | |
174 | { | |
175 | idfmts = collect_formats(msg->id); | |
176 | strfmts = collect_formats(msg->str); | |
177 | fmtidx = 0; | |
178 | ||
179 | for (strfmt = (char *)cupsArrayFirst(strfmts); | |
180 | strfmt; | |
181 | strfmt = (char *)cupsArrayNext(strfmts)) | |
182 | { | |
183 | if (isdigit(strfmt[1] & 255) && strfmt[2] == '$') | |
184 | { | |
185 | /* | |
186 | * Handle positioned format stuff... | |
187 | */ | |
188 | ||
189 | fmtidx = strfmt[1] - '1'; | |
190 | strfmt += 3; | |
191 | if ((idfmt = (char *)cupsArrayIndex(idfmts, fmtidx)) != NULL) | |
192 | idfmt ++; | |
193 | } | |
194 | else | |
195 | { | |
196 | /* | |
197 | * Compare against the current format... | |
198 | */ | |
199 | ||
200 | idfmt = (char *)cupsArrayIndex(idfmts, fmtidx); | |
201 | } | |
202 | ||
203 | fmtidx ++; | |
204 | ||
205 | if (!idfmt || strcmp(strfmt, idfmt)) | |
206 | break; | |
c277e2f8 MS |
207 | } |
208 | ||
209 | if (cupsArrayCount(strfmts) != cupsArrayCount(idfmts) || strfmt) | |
210 | { | |
211 | if (pass) | |
212 | { | |
213 | pass = 0; | |
214 | puts("FAIL"); | |
215 | } | |
216 | ||
217 | printf(" Bad translation string \"%s\"\n for \"%s\"\n", | |
218 | abbreviate(msg->str, strbuf, sizeof(strbuf)), | |
219 | abbreviate(msg->id, idbuf, sizeof(idbuf))); | |
220 | fputs(" Translation formats:", stdout); | |
221 | for (strfmt = (char *)cupsArrayFirst(strfmts); | |
222 | strfmt; | |
223 | strfmt = (char *)cupsArrayNext(strfmts)) | |
224 | printf(" %s", strfmt); | |
225 | fputs("\n Original formats:", stdout); | |
226 | for (idfmt = (char *)cupsArrayFirst(idfmts); | |
227 | idfmt; | |
228 | idfmt = (char *)cupsArrayNext(idfmts)) | |
229 | printf(" %s", idfmt); | |
230 | putchar('\n'); | |
f0ab5bff | 231 | putchar('\n'); |
c277e2f8 MS |
232 | } |
233 | ||
234 | free_formats(idfmts); | |
235 | free_formats(strfmts); | |
236 | } | |
237 | ||
8b116e60 MS |
238 | /* |
239 | * Only allow \\, \n, \r, \t, \", and \### character escapes... | |
240 | */ | |
241 | ||
242 | for (strfmt = msg->str; *strfmt; strfmt ++) | |
243 | if (*strfmt == '\\' && | |
244 | strfmt[1] != '\\' && strfmt[1] != 'n' && strfmt[1] != 'r' && | |
245 | strfmt[1] != 't' && strfmt[1] != '\"' && !isdigit(strfmt[1] & 255)) | |
246 | { | |
247 | if (pass) | |
248 | { | |
249 | pass = 0; | |
250 | puts("FAIL"); | |
251 | } | |
252 | ||
253 | printf(" Bad escape \\%c in filter message \"%s\"\n" | |
0837b7e8 | 254 | " for \"%s\"\n", strfmt[1], |
8b116e60 MS |
255 | abbreviate(msg->str, strbuf, sizeof(strbuf)), |
256 | abbreviate(msg->id, idbuf, sizeof(idbuf))); | |
257 | break; | |
258 | } | |
c277e2f8 MS |
259 | } |
260 | ||
261 | if (pass) | |
262 | { | |
0837b7e8 MS |
263 | if ((untranslated * 10) >= cupsArrayCount(po) && |
264 | strcmp(argv[i], "cups.pot")) | |
c277e2f8 MS |
265 | { |
266 | /* | |
267 | * Only allow 10% of messages to be untranslated before we fail... | |
268 | */ | |
269 | ||
270 | pass = 0; | |
271 | puts("FAIL"); | |
0837b7e8 | 272 | printf(" Too many untranslated messages (%d of %d)\n", |
f0ab5bff | 273 | untranslated, cupsArrayCount(po)); |
c277e2f8 MS |
274 | } |
275 | else if (untranslated > 0) | |
0837b7e8 | 276 | printf("PASS (%d of %d untranslated)\n", untranslated, |
c277e2f8 MS |
277 | cupsArrayCount(po)); |
278 | else | |
0837b7e8 | 279 | puts("PASS"); |
c277e2f8 MS |
280 | } |
281 | ||
282 | if (!pass) | |
283 | status = 1; | |
284 | ||
285 | _cupsMessageFree(po); | |
286 | } | |
287 | ||
288 | return (status); | |
289 | } | |
290 | ||
291 | ||
292 | /* | |
293 | * 'abbreviate()' - Abbreviate a message string as needed. | |
294 | */ | |
295 | ||
296 | static char * /* O - Abbreviated string */ | |
297 | abbreviate(const char *s, /* I - String to abbreviate */ | |
298 | char *buf, /* I - Buffer */ | |
299 | int bufsize) /* I - Size of buffer */ | |
300 | { | |
301 | char *bufptr; /* Pointer into buffer */ | |
302 | ||
303 | ||
304 | for (bufptr = buf, bufsize -= 4; *s && bufsize > 0; s ++) | |
305 | { | |
306 | if (*s == '\n') | |
307 | { | |
308 | if (bufsize < 2) | |
309 | break; | |
310 | ||
311 | *bufptr++ = '\\'; | |
312 | *bufptr++ = 'n'; | |
313 | bufsize -= 2; | |
314 | } | |
315 | else if (*s == '\t') | |
316 | { | |
317 | if (bufsize < 2) | |
318 | break; | |
319 | ||
320 | *bufptr++ = '\\'; | |
321 | *bufptr++ = 't'; | |
322 | bufsize -= 2; | |
323 | } | |
324 | else if (*s >= 0 && *s < ' ') | |
325 | { | |
326 | if (bufsize < 4) | |
327 | break; | |
328 | ||
329 | sprintf(bufptr, "\\%03o", *s); | |
330 | bufptr += 4; | |
331 | bufsize -= 4; | |
332 | } | |
333 | else | |
334 | { | |
335 | *bufptr++ = *s; | |
336 | bufsize --; | |
337 | } | |
338 | } | |
339 | ||
340 | if (*s) | |
5a9febac | 341 | memcpy(bufptr, "...", 4); |
c277e2f8 MS |
342 | else |
343 | *bufptr = '\0'; | |
344 | ||
345 | return (buf); | |
346 | } | |
347 | ||
348 | ||
349 | /* | |
350 | * 'collect_formats()' - Collect all of the format strings in the msgid. | |
351 | */ | |
352 | ||
353 | static cups_array_t * /* O - Array of format strings */ | |
354 | collect_formats(const char *id) /* I - msgid string */ | |
355 | { | |
356 | cups_array_t *fmts; /* Array of format strings */ | |
357 | char buf[255], /* Format string buffer */ | |
358 | *bufptr; /* Pointer into format string */ | |
359 | ||
360 | ||
361 | fmts = cupsArrayNew(NULL, NULL); | |
362 | ||
363 | while ((id = strchr(id, '%')) != NULL) | |
364 | { | |
365 | if (id[1] == '%') | |
366 | { | |
367 | /* | |
368 | * Skip %%... | |
369 | */ | |
370 | ||
371 | id += 2; | |
372 | continue; | |
373 | } | |
374 | ||
375 | for (bufptr = buf; *id && bufptr < (buf + sizeof(buf) - 1); id ++) | |
376 | { | |
377 | *bufptr++ = *id; | |
378 | ||
379 | if (strchr("CDEFGIOSUXcdeifgopsux", *id)) | |
380 | { | |
381 | id ++; | |
382 | break; | |
383 | } | |
384 | } | |
385 | ||
386 | *bufptr = '\0'; | |
387 | cupsArrayAdd(fmts, strdup(buf)); | |
388 | } | |
389 | ||
390 | return (fmts); | |
391 | } | |
392 | ||
393 | ||
394 | /* | |
395 | * 'free_formats()' - Free all of the format strings. | |
396 | */ | |
397 | ||
398 | static void | |
399 | free_formats(cups_array_t *fmts) /* I - Array of format strings */ | |
400 | { | |
401 | char *s; /* Current string */ | |
402 | ||
403 | ||
404 | for (s = (char *)cupsArrayFirst(fmts); s; s = (char *)cupsArrayNext(fmts)) | |
405 | free(s); | |
406 | ||
407 | cupsArrayDelete(fmts); | |
408 | } | |
409 | ||
410 | ||
411 | /* | |
75bd9771 | 412 | * End of "$Id: checkpo.c 7223 2008-01-16 23:41:19Z mike $". |
c277e2f8 | 413 | */ |