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