]> git.ipfire.org Git - thirdparty/cups.git/blob - locale/checkpo.c
Merge changes from CUPS 1.5svn-r9049 (private header support)
[thirdparty/cups.git] / locale / checkpo.c
1 /*
2 * "$Id: checkpo.c 7223 2008-01-16 23:41:19Z mike $"
3 *
4 * Verify that translations in the .po file have the same number and type of
5 * printf-style format strings.
6 *
7 * Copyright 2007-2010 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 *
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 <cups/cups-private.h>
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 */
59 int fmtidx; /* Format index */
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
83 if ((po = _cupsMessageLoad(argv[i], 0)) == NULL)
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;
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');
167 putchar('\n');
168 }
169
170 free_formats(idfmts);
171 free_formats(strfmts);
172 }
173
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
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
216 printf(" Bad prefix on filter message \"%s\"\n for \"%s\"\n\n",
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");
232 printf(" Too many untranslated messages (%d of %d)\n\n",
233 untranslated, cupsArrayCount(po));
234 }
235 else if (untranslated > 0)
236 printf("PASS (%d of %d untranslated)\n\n", untranslated,
237 cupsArrayCount(po));
238 else
239 puts("PASS\n");
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 /*
372 * End of "$Id: checkpo.c 7223 2008-01-16 23:41:19Z mike $".
373 */