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