]> git.ipfire.org Git - thirdparty/cups.git/blame - locale/po2strings.c
Changelog
[thirdparty/cups.git] / locale / po2strings.c
CommitLineData
bc44d920 1/*
7e86f2f6 2 * Convert a GNU gettext .po file to an Apple .strings file.
71e16022 3 *
5423798c 4 * Copyright 2007-2017 by Apple Inc.
71e16022 5 *
e3101897 6 * Licensed under Apache License v2.0. See the file "LICENSE" for more information.
bc44d920 7 *
8 * Usage:
9 *
10 * po2strings filename.strings filename.po
11 *
12 * Compile with:
13 *
14 * gcc -o po2strings po2strings.c `cups-config --libs`
bc44d920 15 */
16
71e16022 17#include <cups/cups-private.h>
bc44d920 18
19
20/*
21 * The .strings file format is simple:
22 *
23 * // comment
0837b7e8 24 * "msgid" = "msgstr";
bc44d920 25 *
0837b7e8
MS
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.
bc44d920 44 */
45
3b20f9f5
MS
46static char *normalize_string(const char *idstr, char *buffer, size_t bufsize);
47
48
bc44d920 49/*
50 * main() - Convert .po file to .strings.
51 */
52
53int /* O - Exit code */
54main(int argc, /* I - Number of command-line args */
55 char *argv[]) /* I - Command-line arguments */
56{
745129be 57 int i; /* Looping var */
0837b7e8
MS
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 */
3b20f9f5
MS
66 *msgstr, /* msgstr string */
67 normalized[8192];/* Normalized msgid string */
7e86f2f6 68 size_t length; /* Length of combined strings */
745129be 69 int use_msgid; /* Use msgid strings for msgstr? */
bc44d920 70
71
0837b7e8
MS
72 /*
73 * Process command-line arguments...
74 */
745129be 75
0837b7e8
MS
76 pofile = NULL;
77 stringsfile = NULL;
78 use_msgid = 0;
745129be
MS
79
80 for (i = 1; i < argc; i ++)
0837b7e8 81 {
745129be
MS
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 }
0837b7e8
MS
89 else if (!pofile)
90 pofile = argv[i];
91 else if (!stringsfile)
92 stringsfile = argv[i];
745129be
MS
93 else
94 {
95 puts("Usage: po2strings [-m] filename.po filename.strings");
96 return (1);
97 }
0837b7e8 98 }
745129be 99
0837b7e8 100 if (!pofile || !stringsfile)
bc44d920 101 {
745129be 102 puts("Usage: po2strings [-m] filename.po filename.strings");
bc44d920 103 return (1);
104 }
105
106 /*
0837b7e8 107 * Read strings from the .po file and write to the .strings file...
bc44d920 108 */
109
0837b7e8 110 if ((po = cupsFileOpen(pofile, "r")) == NULL)
bc44d920 111 {
0837b7e8 112 perror(pofile);
bc44d920 113 return (1);
114 }
115
0837b7e8 116 if ((strings = cupsFileOpen(stringsfile, "w")) == NULL)
bc44d920 117 {
0837b7e8
MS
118 perror(stringsfile);
119 cupsFileClose(po);
bc44d920 120 return (1);
121 }
122
0837b7e8
MS
123 msgid = msgstr = NULL;
124
125 while (cupsFileGets(po, s, sizeof(s)) != NULL)
bc44d920 126 {
84315f46 127 if (s[0] == '#' && s[1] == '.')
0837b7e8
MS
128 {
129 /*
84315f46 130 * Copy comment string...
0837b7e8 131 */
bc44d920 132
84315f46
MS
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);
0837b7e8 149 }
84315f46 150 else if (s[0] == '#' || !s[0])
0837b7e8
MS
151 {
152 /*
84315f46 153 * Skip blank and file comment lines...
0837b7e8 154 */
bc44d920 155
84315f46 156 continue;
0837b7e8
MS
157 }
158 else
159 {
160 /*
161 * Strip the trailing quote...
162 */
bc44d920 163
0837b7e8
MS
164 if ((ptr = strrchr(s, '\"')) == NULL)
165 continue;
bc44d920 166
0837b7e8 167 *ptr = '\0';
bc44d920 168
0837b7e8
MS
169 /*
170 * Find start of value...
171 */
82cc1f9a 172
0837b7e8
MS
173 if ((ptr = strchr(s, '\"')) == NULL)
174 continue;
bc44d920 175
0837b7e8 176 ptr ++;
bc44d920 177
0837b7e8
MS
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)
3b20f9f5 191 cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));
82cc1f9a 192 }
0837b7e8 193
82cc1f9a 194 if (msgid)
0837b7e8 195 free(msgid);
82cc1f9a
MS
196
197 if (msgstr)
0837b7e8 198 free(msgstr);
0837b7e8
MS
199
200 msgid = strdup(ptr);
201 msgstr = NULL;
202 }
82cc1f9a 203 else if (s[0] == '\"' && (msgid || msgstr))
0837b7e8
MS
204 {
205 /*
206 * Append to current string...
207 */
208
5a9febac
MS
209 size_t ptrlen = strlen(ptr); /* Length of string */
210
7e86f2f6 211 length = strlen(msgstr ? msgstr : msgid);
0837b7e8
MS
212
213 if ((temp = realloc(msgstr ? msgstr : msgid,
5a9febac 214 length + ptrlen + 1)) == NULL)
0837b7e8 215 {
82cc1f9a
MS
216 free(msgid);
217 if (msgstr)
218 free(msgstr);
0837b7e8
MS
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
5a9febac 233 memcpy(msgstr + length, ptr, ptrlen + 1);
0837b7e8
MS
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
5a9febac 245 memcpy(msgid + length, ptr, ptrlen + 1);
0837b7e8
MS
246 }
247 }
248 else if (!strncmp(s, "msgstr", 6) && msgid)
249 {
250 /*
251 * Set the string...
252 */
253
82cc1f9a
MS
254 if (msgstr)
255 free(msgstr);
256
0837b7e8
MS
257 if ((msgstr = strdup(ptr)) == NULL)
258 {
82cc1f9a 259 free(msgid);
0837b7e8
MS
260 perror("Unable to allocate msgstr");
261 return (1);
262 }
263 }
bc44d920 264 }
0837b7e8
MS
265 }
266
267 if (msgid && msgstr)
268 {
269 if (*msgid)
3b20f9f5 270 cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));
82cc1f9a 271 }
bc44d920 272
82cc1f9a 273 if (msgid)
0837b7e8 274 free(msgid);
82cc1f9a
MS
275
276 if (msgstr)
0837b7e8 277 free(msgstr);
bc44d920 278
0837b7e8
MS
279 cupsFileClose(po);
280 cupsFileClose(strings);
281
282 return (0);
bc44d920 283}
284
285
3b20f9f5
MS
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
293static char * /* O - Normalized string */
294normalize_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 */
08b1b9ba
MS
300 int quote = 0, /* Quote direction */
301 html = 0; /* HTML text */
3b20f9f5
MS
302
303
304 while (*idstr && bufptr < bufend)
305 {
08b1b9ba
MS
306 if (!strncmp(idstr, "<A ", 3))
307 html = 1;
308 else if (html && *idstr == '>')
309 html = 0;
310
3b20f9f5
MS
311 if (*idstr == '.' && idstr[1] == '.' && idstr[2] == '.')
312 {
313 /*
71936a32 314 * Convert ... to Unicode ellipsis...
3b20f9f5
MS
315 */
316
317 *bufptr++ = (char)0xE2;
318 *bufptr++ = (char)0x80;
319 *bufptr++ = (char)0xA6;
320 idstr += 2;
321 }
5423798c 322 else if (!html && *idstr == '\\' && idstr[1] == '\"')
3b20f9f5
MS
323 {
324 if (quote)
325 {
326 /*
5423798c 327 * Convert second \" to Unicode right (curley) double quote.
3b20f9f5
MS
328 */
329
330 *bufptr++ = (char)0xE2;
331 *bufptr++ = (char)0x80;
332 *bufptr++ = (char)0x9D;
5423798c 333 quote = 0;
3b20f9f5 334 }
5423798c 335 else if (strchr(idstr + 2, '\"') != NULL)
3b20f9f5
MS
336 {
337 /*
5423798c 338 * Convert first \" to Unicode left (curley) double quote.
3b20f9f5
MS
339 */
340
341 *bufptr++ = (char)0xE2;
342 *bufptr++ = (char)0x80;
343 *bufptr++ = (char)0x9C;
5423798c
MS
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;
3b20f9f5
MS
355 }
356
71936a32 357 idstr ++;
3b20f9f5 358 }
17acb469
MS
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 }
17acb469 384 }
3b20f9f5
MS
385 else
386 *bufptr++ = *idstr;
387
388 idstr ++;
389 }
390
391 *bufptr = '\0';
392
393 return (buffer);
394}