1 /*
2 * Convert a GNU gettext .po file to an Apple .strings file.
3 *
4 * Copyright 2007-2017 by Apple Inc.
5 *
6 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
7 *
8 * Usage:
9 *
10 * po2strings filename.strings filename.po
11 *
12 * Compile with:
13 *
14 * gcc -o po2strings po2strings.c `cups-config --libs`
15 */
16
17 #include <cups/cups-private.h>
18
19
20 /*
21 * The .strings file format is simple:
22 *
23 * // comment
24 * "msgid" = "msgstr";
25 *
26 * The GNU gettext .po format is also fairly simple:
27 *
28 * #. comment
29 * msgid "some text"
30 * msgstr "localized text"
31 *
32 * The comment, msgid, and msgstr text can span multiple lines using the form:
33 *
34 * #. comment
35 * #. more comments
36 * msgid ""
37 * "some long text"
38 * msgstr ""
39 * "localized text spanning "
40 * "multiple lines"
41 *
42 * Both the msgid and msgstr strings use standard C quoting for special
43 * characters like newline and the double quote character.
44 */
45
46 static char *normalize_string(const char *idstr, char *buffer, size_t bufsize);
47
48
49 /*
50 * main() - Convert .po file to .strings.
51 */
52
53 int /* O - Exit code */
main(int argc,char * argv[])54 main(int argc, /* I - Number of command-line args */
55 char *argv[]) /* I - Command-line arguments */
56 {
57 int i; /* Looping var */
58 const char *pofile, /* .po filename */
59 *stringsfile; /* .strings filename */
60 cups_file_t *po, /* .po file */
61 *strings; /* .strings file */
62 char s[4096], /* String buffer */
63 *ptr, /* Pointer into buffer */
64 *temp, /* New string */
65 *msgid, /* msgid string */
66 *msgstr, /* msgstr string */
67 normalized[8192];/* Normalized msgid string */
68 size_t length; /* Length of combined strings */
69 int use_msgid; /* Use msgid strings for msgstr? */
70
71
72 /*
73 * Process command-line arguments...
74 */
75
76 pofile = NULL;
77 stringsfile = NULL;
78 use_msgid = 0;
79
80 for (i = 1; i < argc; i ++)
81 {
82 if (!strcmp(argv[i], "-m"))
83 use_msgid = 1;
84 else if (argv[i][0] == '-')
85 {
86 puts("Usage: po2strings [-m] filename.po filename.strings");
87 return (1);
88 }
89 else if (!pofile)
90 pofile = argv[i];
91 else if (!stringsfile)
92 stringsfile = argv[i];
93 else
94 {
95 puts("Usage: po2strings [-m] filename.po filename.strings");
96 return (1);
97 }
98 }
99
100 if (!pofile || !stringsfile)
101 {
102 puts("Usage: po2strings [-m] filename.po filename.strings");
103 return (1);
104 }
105
106 /*
107 * Read strings from the .po file and write to the .strings file...
108 */
109
110 if ((po = cupsFileOpen(pofile, "r")) == NULL)
111 {
112 perror(pofile);
113 return (1);
114 }
115
116 if ((strings = cupsFileOpen(stringsfile, "w")) == NULL)
117 {
118 perror(stringsfile);
119 cupsFileClose(po);
120 return (1);
121 }
122
123 msgid = msgstr = NULL;
124
125 while (cupsFileGets(po, s, sizeof(s)) != NULL)
126 {
127 if (s[0] == '#' && s[1] == '.')
128 {
129 /*
130 * Copy comment string...
131 */
132
133 if (msgid && msgstr)
134 {
135 /*
136 * First output the last localization string...
137 */
138
139 if (*msgid)
140 cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid,
141 (use_msgid || !*msgstr) ? msgid : msgstr);
142
143 free(msgid);
144 free(msgstr);
145 msgid = msgstr = NULL;
146 }
147
148 cupsFilePrintf(strings, "//%s\n", s + 2);
149 }
150 else if (s[0] == '#' || !s[0])
151 {
152 /*
153 * Skip blank and file comment lines...
154 */
155
156 continue;
157 }
158 else
159 {
160 /*
161 * Strip the trailing quote...
162 */
163
164 if ((ptr = strrchr(s, '\"')) == NULL)
165 continue;
166
167 *ptr = '\0';
168
169 /*
170 * Find start of value...
171 */
172
173 if ((ptr = strchr(s, '\"')) == NULL)
174 continue;
175
176 ptr ++;
177
178 /*
179 * Create or add to a message...
180 */
181
182 if (!strncmp(s, "msgid", 5))
183 {
184 /*
185 * Output previous message as needed...
186 */
187
188 if (msgid && msgstr)
189 {
190 if (*msgid)
191 cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));
192 }
193
194 if (msgid)
195 free(msgid);
196
197 if (msgstr)
198 free(msgstr);
199
200 msgid = strdup(ptr);
201 msgstr = NULL;
202 }
203 else if (s[0] == '\"' && (msgid || msgstr))
204 {
205 /*
206 * Append to current string...
207 */
208
209 size_t ptrlen = strlen(ptr); /* Length of string */
210
211 length = strlen(msgstr ? msgstr : msgid);
212
213 if ((temp = realloc(msgstr ? msgstr : msgid,
214 length + ptrlen + 1)) == NULL)
215 {
216 free(msgid);
217 if (msgstr)
218 free(msgstr);
219 perror("Unable to allocate string");
220 return (1);
221 }
222
223 if (msgstr)
224 {
225 /*
226 * Copy the new portion to the end of the msgstr string - safe
227 * to use strcpy because the buffer is allocated to the correct
228 * size...
229 */
230
231 msgstr = temp;
232
233 memcpy(msgstr + length, ptr, ptrlen + 1);
234 }
235 else
236 {
237 /*
238 * Copy the new portion to the end of the msgid string - safe
239 * to use strcpy because the buffer is allocated to the correct
240 * size...
241 */
242
243 msgid = temp;
244
245 memcpy(msgid + length, ptr, ptrlen + 1);
246 }
247 }
248 else if (!strncmp(s, "msgstr", 6) && msgid)
249 {
250 /*
251 * Set the string...
252 */
253
254 if (msgstr)
255 free(msgstr);
256
257 if ((msgstr = strdup(ptr)) == NULL)
258 {
259 free(msgid);
260 perror("Unable to allocate msgstr");
261 return (1);
262 }
263 }
264 }
265 }
266
267 if (msgid && msgstr)
268 {
269 if (*msgid)
270 cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));
271 }
272
273 if (msgid)
274 free(msgid);
275
276 if (msgstr)
277 free(msgstr);
278
279 cupsFileClose(po);
280 cupsFileClose(strings);
281
282 return (0);
283 }
284
285
286 /*
287 * 'normalize_string()' - Normalize a msgid string.
288 *
289 * This function converts ASCII ellipsis and double quotes to their Unicode
290 * counterparts.
291 */
292
293 static char * /* O - Normalized string */
normalize_string(const char * idstr,char * buffer,size_t bufsize)294 normalize_string(const char *idstr, /* I - msgid string */
295 char *buffer, /* I - Normalized string buffer */
296 size_t bufsize) /* I - Size of string buffer */
297 {
298 char *bufptr = buffer, /* Pointer into buffer */
299 *bufend = buffer + bufsize - 3; /* End of buffer */
300 int quote = 0, /* Quote direction */
301 html = 0; /* HTML text */
302
303
304 while (*idstr && bufptr < bufend)
305 {
306 if (!strncmp(idstr, "<A ", 3))
307 html = 1;
308 else if (html && *idstr == '>')
309 html = 0;
310
311 if (*idstr == '.' && idstr[1] == '.' && idstr[2] == '.')
312 {
313 /*
314 * Convert ... to Unicode ellipsis...
315 */
316
317 *bufptr++ = (char)0xE2;
318 *bufptr++ = (char)0x80;
319 *bufptr++ = (char)0xA6;
320 idstr += 2;
321 }
322 else if (!html && *idstr == '\\' && idstr[1] == '\"')
323 {
324 if (quote)
325 {
326 /*
327 * Convert second \" to Unicode right (curley) double quote.
328 */
329
330 *bufptr++ = (char)0xE2;
331 *bufptr++ = (char)0x80;
332 *bufptr++ = (char)0x9D;
333 quote = 0;
334 }
335 else if (strchr(idstr + 2, '\"') != NULL)
336 {
337 /*
338 * Convert first \" to Unicode left (curley) double quote.
339 */
340
341 *bufptr++ = (char)0xE2;
342 *bufptr++ = (char)0x80;
343 *bufptr++ = (char)0x9C;
344 quote = 1;
345 }
346 else
347 {
348 /*
349 * Convert lone \" to Unicode double prime.
350 */
351
352 *bufptr++ = (char)0xE2;
353 *bufptr++ = (char)0x80;
354 *bufptr++ = (char)0xB3;
355 }
356
357 idstr ++;
358 }
359 else if (*idstr == '\'')
360 {
361 if (strchr(idstr + 1, '\'') == NULL || quote)
362 {
363 /*
364 * Convert second ' (or ' used for a contraction) to Unicode right
365 * (curley) single quote.
366 */
367
368 *bufptr++ = (char)0xE2;
369 *bufptr++ = (char)0x80;
370 *bufptr++ = (char)0x99;
371 quote = 0;
372 }
373 else
374 {
375 /*
376 * Convert first ' to Unicode left (curley) single quote.
377 */
378
379 *bufptr++ = (char)0xE2;
380 *bufptr++ = (char)0x80;
381 *bufptr++ = (char)0x98;
382 quote = 1;
383 }
384 }
385 else
386 *bufptr++ = *idstr;
387
388 idstr ++;
389 }
390
391 *bufptr = '\0';
392
393 return (buffer);
394 }
395