]>
Commit | Line | Data |
---|---|---|
ac884b6a | 1 | // |
7e86f2f6 | 2 | // Shared message catalog class for the CUPS PPD Compiler. |
ac884b6a | 3 | // |
47386fc1 | 4 | // Copyright 2007-2017 by Apple Inc. |
7e86f2f6 | 5 | // Copyright 2002-2006 by Easy Software Products. |
ac884b6a | 6 | // |
e3101897 | 7 | // Licensed under Apache License v2.0. See the file "LICENSE" for more information. |
ac884b6a MS |
8 | // |
9 | ||
10 | // | |
11 | // Include necessary headers... | |
12 | // | |
13 | ||
38e73f87 | 14 | #include "ppdc-private.h" |
ac884b6a MS |
15 | |
16 | ||
839a51c8 MS |
17 | // |
18 | // Character encodings... | |
19 | // | |
20 | ||
21 | typedef enum | |
22 | { | |
23 | PPDC_CS_AUTO, | |
24 | PPDC_CS_UTF8, | |
25 | PPDC_CS_UTF16BE, | |
26 | PPDC_CS_UTF16LE | |
27 | } ppdc_cs_t; | |
28 | ||
29 | ||
30 | // | |
31 | // Local functions... | |
32 | // | |
33 | ||
be76a973 MS |
34 | #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR) |
35 | static void apple_add_message(CFStringRef key, CFStringRef val, ppdcCatalog *c); | |
36 | #endif /* __APPLE__ && CUPS_BUNDLEDIR */ | |
839a51c8 MS |
37 | static int get_utf8(char *&ptr); |
38 | static int get_utf16(cups_file_t *fp, ppdc_cs_t &cs); | |
39 | static int put_utf8(int ch, char *&ptr, char *end); | |
40 | static int put_utf16(cups_file_t *fp, int ch); | |
41 | ||
42 | ||
ac884b6a MS |
43 | // |
44 | // 'ppdcCatalog::ppdcCatalog()' - Create a shared message catalog. | |
45 | // | |
46 | ||
47 | ppdcCatalog::ppdcCatalog(const char *l, // I - Locale | |
48 | const char *f) // I - Message catalog file | |
49 | : ppdcShared() | |
50 | { | |
94da7e34 MS |
51 | PPDC_NEW; |
52 | ||
ac884b6a MS |
53 | locale = new ppdcString(l); |
54 | filename = new ppdcString(f); | |
55 | messages = new ppdcArray(); | |
56 | ||
3aca6b55 | 57 | if (l && strcmp(l, "en")) |
ac884b6a MS |
58 | { |
59 | // Try loading the base messages for this locale... | |
60 | char pofile[1024]; // Message catalog file | |
61 | ||
62 | ||
be76a973 MS |
63 | #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR) |
64 | char applelang[256]; // Apple language ID | |
65 | CFURLRef url; // URL to cups.strings file | |
66 | CFReadStreamRef stream = NULL; // File stream | |
67 | CFPropertyListRef plist = NULL; // Localization file | |
68 | ||
69 | snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", _cupsAppleLanguage(l, applelang, sizeof(applelang))); | |
70 | if (access(pofile, 0)) | |
71 | { | |
72 | // Try alternate lproj directory names... | |
73 | const char *tl = l; // Temporary locale string | |
74 | ||
75 | if (!strncmp(l, "en", 2)) | |
76 | tl = "English"; | |
b052deed MS |
77 | else if (!strncmp(l, "nb", 2)) |
78 | tl = "no"; | |
79 | else if (!strncmp(l, "nl", 2)) | |
be76a973 MS |
80 | tl = "Dutch"; |
81 | else if (!strncmp(l, "fr", 2)) | |
82 | tl = "French"; | |
83 | else if (!strncmp(l, "de", 2)) | |
84 | tl = "German"; | |
85 | else if (!strncmp(l, "it", 2)) | |
86 | tl = "Italian"; | |
87 | else if (!strncmp(l, "ja", 2)) | |
88 | tl = "Japanese"; | |
89 | else if (!strncmp(l, "es", 2)) | |
90 | tl = "Spanish"; | |
91 | ||
92 | snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", tl); | |
93 | } | |
94 | ||
95 | url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8 *)pofile, (CFIndex)strlen(pofile), false); | |
96 | if (url) | |
97 | { | |
98 | stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url); | |
99 | ||
100 | if (stream) | |
101 | { | |
102 | /* | |
103 | * Read the property list containing the localization data. | |
104 | */ | |
105 | ||
106 | CFReadStreamOpen(stream); | |
107 | ||
108 | plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, NULL, NULL); | |
109 | ||
110 | if (plist && CFGetTypeID(plist) == CFDictionaryGetTypeID()) | |
111 | CFDictionaryApplyFunction((CFDictionaryRef)plist, (CFDictionaryApplierFunction)apple_add_message, this); | |
112 | ||
113 | if (plist) | |
114 | CFRelease(plist); | |
115 | ||
116 | CFRelease(stream); | |
117 | } | |
118 | ||
119 | CFRelease(url); | |
120 | } | |
121 | ||
122 | #else | |
123 | _cups_globals_t *cg = _cupsGlobals(); | |
124 | // Global information | |
125 | ||
61cf44e2 | 126 | snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir, l, l); |
ac884b6a MS |
127 | |
128 | if (load_messages(pofile) && strchr(l, '_')) | |
129 | { | |
130 | // Try the base locale... | |
131 | char baseloc[3]; // Base locale... | |
132 | ||
133 | ||
134 | strlcpy(baseloc, l, sizeof(baseloc)); | |
61cf44e2 | 135 | snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir, |
ac884b6a MS |
136 | baseloc, baseloc); |
137 | ||
138 | load_messages(pofile); | |
139 | } | |
be76a973 | 140 | #endif /* __APPLE__ && CUPS_BUNDLEDIR */ |
ac884b6a MS |
141 | } |
142 | ||
be76a973 | 143 | if (f && *f) |
ac884b6a MS |
144 | load_messages(f); |
145 | } | |
146 | ||
147 | ||
148 | // | |
149 | // 'ppdcCatalog::~ppdcCatalog()' - Destroy a shared message catalog. | |
150 | // | |
151 | ||
152 | ppdcCatalog::~ppdcCatalog() | |
153 | { | |
94da7e34 MS |
154 | PPDC_DELETE; |
155 | ||
e4572d57 MS |
156 | locale->release(); |
157 | filename->release(); | |
158 | messages->release(); | |
ac884b6a MS |
159 | } |
160 | ||
161 | ||
162 | // | |
163 | // 'ppdcCatalog::add_message()' - Add a new message. | |
164 | // | |
165 | ||
166 | void | |
61cf44e2 MS |
167 | ppdcCatalog::add_message( |
168 | const char *id, // I - Message ID to add | |
169 | const char *string) // I - Translation string | |
ac884b6a MS |
170 | { |
171 | ppdcMessage *m; // Current message | |
172 | char text[1024]; // Text to translate | |
173 | ||
174 | ||
175 | // Range check input... | |
61cf44e2 | 176 | if (!id) |
ac884b6a MS |
177 | return; |
178 | ||
179 | // Verify that we don't already have the message ID... | |
180 | for (m = (ppdcMessage *)messages->first(); | |
181 | m; | |
182 | m = (ppdcMessage *)messages->next()) | |
183 | if (!strcmp(m->id->value, id)) | |
61cf44e2 MS |
184 | { |
185 | if (string) | |
186 | { | |
187 | m->string->release(); | |
188 | m->string = new ppdcString(string); | |
189 | } | |
ac884b6a | 190 | return; |
61cf44e2 | 191 | } |
ac884b6a MS |
192 | |
193 | // Add the message... | |
61cf44e2 MS |
194 | if (!string) |
195 | { | |
196 | snprintf(text, sizeof(text), "TRANSLATE %s", id); | |
197 | string = text; | |
198 | } | |
199 | ||
e6013cfa | 200 | messages->add(new ppdcMessage(id, string)); |
ac884b6a MS |
201 | } |
202 | ||
203 | ||
204 | // | |
205 | // 'ppdcCatalog::find_message()' - Find a message in a catalog... | |
206 | // | |
207 | ||
208 | const char * // O - Message text | |
209 | ppdcCatalog::find_message( | |
210 | const char *id) // I - Message ID | |
211 | { | |
212 | ppdcMessage *m; // Current message | |
213 | ||
214 | ||
ef55b745 MS |
215 | if (!*id) |
216 | return (id); | |
217 | ||
ac884b6a MS |
218 | for (m = (ppdcMessage *)messages->first(); |
219 | m; | |
220 | m = (ppdcMessage *)messages->next()) | |
221 | if (!strcmp(m->id->value, id)) | |
222 | return (m->string->value); | |
223 | ||
224 | return (id); | |
225 | } | |
226 | ||
227 | ||
228 | // | |
229 | // 'ppdcCatalog::load_messages()' - Load messages from a .po file. | |
230 | // | |
231 | ||
232 | int // O - 0 on success, -1 on failure | |
233 | ppdcCatalog::load_messages( | |
234 | const char *f) // I - Message catalog file | |
235 | { | |
236 | cups_file_t *fp; // Message file | |
ac884b6a MS |
237 | char line[4096], // Line buffer |
238 | *ptr, // Pointer into buffer | |
239 | id[4096], // Translation ID | |
240 | str[4096]; // Translation string | |
241 | int linenum; // Line number | |
242 | ||
243 | ||
244 | // Open the message catalog file... | |
245 | if ((fp = cupsFileOpen(f, "r")) == NULL) | |
246 | return (-1); | |
247 | ||
ae71f5de | 248 | if ((ptr = (char *)strrchr(f, '.')) == NULL) |
839a51c8 MS |
249 | goto unknown_load_format; |
250 | else if (!strcmp(ptr, ".strings")) | |
251 | { | |
252 | /* | |
8072030b | 253 | * Read messages in macOS ".strings" format, which are either UTF-8/UTF-16 |
be76a973 | 254 | * text files of the format: |
839a51c8 MS |
255 | * |
256 | * "id" = "str"; | |
257 | * | |
258 | * Strings files can also contain C-style comments. | |
259 | */ | |
260 | ||
261 | ppdc_cs_t cs = PPDC_CS_AUTO; // Character set for file | |
262 | int ch; // Current character from file | |
263 | char *end; // End of buffer | |
264 | ||
265 | ||
266 | id[0] = '\0'; | |
267 | str[0] = '\0'; | |
268 | ptr = NULL; | |
269 | end = NULL; | |
270 | ||
271 | while ((ch = get_utf16(fp, cs)) != 0) | |
272 | { | |
273 | if (ptr) | |
274 | { | |
275 | if (ch == '\\') | |
276 | { | |
277 | if ((ch = get_utf16(fp, cs)) == 0) | |
278 | break; | |
ac884b6a | 279 | |
4509bb49 | 280 | if (ch == 'n') |
839a51c8 MS |
281 | ch = '\n'; |
282 | else if (ch == 't') | |
283 | ch = '\t'; | |
284 | } | |
4509bb49 MS |
285 | else if (ch == '\"') |
286 | { | |
287 | *ptr = '\0'; | |
288 | ptr = NULL; | |
289 | } | |
ac884b6a | 290 | |
4509bb49 MS |
291 | if (ptr) |
292 | put_utf8(ch, ptr, end); | |
839a51c8 MS |
293 | } |
294 | else if (ch == '/') | |
295 | { | |
296 | // Start of a comment? | |
297 | if ((ch = get_utf16(fp, cs)) == 0) | |
298 | break; | |
ac884b6a | 299 | |
839a51c8 MS |
300 | if (ch == '*') |
301 | { | |
302 | // Skip C comment... | |
303 | int lastch = 0; | |
ac884b6a | 304 | |
839a51c8 MS |
305 | while ((ch = get_utf16(fp, cs)) != 0) |
306 | { | |
307 | if (ch == '/' && lastch == '*') | |
308 | break; | |
ac884b6a | 309 | |
839a51c8 MS |
310 | lastch = ch; |
311 | } | |
312 | } | |
313 | else if (ch == '/') | |
314 | { | |
315 | // Skip C++ comment... | |
316 | while ((ch = get_utf16(fp, cs)) != 0) | |
317 | if (ch == '\n') | |
318 | break; | |
319 | } | |
320 | } | |
321 | else if (ch == '\"') | |
322 | { | |
4509bb49 MS |
323 | // Start quoted string... |
324 | if (id[0]) | |
839a51c8 MS |
325 | { |
326 | ptr = str; | |
327 | end = str + sizeof(str) - 1; | |
328 | } | |
329 | else | |
330 | { | |
331 | ptr = id; | |
332 | end = id + sizeof(id) - 1; | |
333 | } | |
334 | } | |
335 | else if (ch == ';') | |
336 | { | |
337 | // Add string... | |
61cf44e2 | 338 | add_message(id, str); |
e6013cfa | 339 | id[0] = '\0'; |
839a51c8 | 340 | } |
ac884b6a | 341 | } |
839a51c8 MS |
342 | } |
343 | else if (!strcmp(ptr, ".po") || !strcmp(ptr, ".gz")) | |
344 | { | |
345 | /* | |
346 | * Read messages from the catalog file until EOF... | |
347 | * | |
348 | * The format is the GNU gettext .po format, which is fairly simple: | |
349 | * | |
350 | * msgid "some text" | |
351 | * msgstr "localized text" | |
352 | * | |
353 | * The ID and localized text can span multiple lines using the form: | |
354 | * | |
355 | * msgid "" | |
356 | * "some long text" | |
357 | * msgstr "" | |
358 | * "localized text spanning " | |
359 | * "multiple lines" | |
360 | */ | |
361 | ||
61cf44e2 MS |
362 | int which, // In msgid? |
363 | haveid, // Did we get a msgid string? | |
364 | havestr; // Did we get a msgstr string? | |
f3c17241 | 365 | |
839a51c8 MS |
366 | linenum = 0; |
367 | id[0] = '\0'; | |
368 | str[0] = '\0'; | |
61cf44e2 MS |
369 | haveid = 0; |
370 | havestr = 0; | |
371 | which = 0; | |
839a51c8 MS |
372 | |
373 | while (cupsFileGets(fp, line, sizeof(line))) | |
374 | { | |
375 | linenum ++; | |
ac884b6a | 376 | |
839a51c8 MS |
377 | // Skip blank and comment lines... |
378 | if (line[0] == '#' || !line[0]) | |
379 | continue; | |
ac884b6a | 380 | |
839a51c8 | 381 | // Strip the trailing quote... |
ae71f5de | 382 | if ((ptr = (char *)strrchr(line, '\"')) == NULL) |
839a51c8 | 383 | { |
61cf44e2 | 384 | _cupsLangPrintf(stderr, |
0837b7e8 | 385 | _("ppdc: Expected quoted string on line %d of %s."), |
61cf44e2 | 386 | linenum, f); |
839a51c8 MS |
387 | cupsFileClose(fp); |
388 | return (-1); | |
389 | } | |
ac884b6a | 390 | |
839a51c8 MS |
391 | *ptr = '\0'; |
392 | ||
393 | // Find start of value... | |
394 | if ((ptr = strchr(line, '\"')) == NULL) | |
395 | { | |
61cf44e2 | 396 | _cupsLangPrintf(stderr, |
0837b7e8 | 397 | _("ppdc: Expected quoted string on line %d of %s."), |
61cf44e2 | 398 | linenum, f); |
839a51c8 MS |
399 | cupsFileClose(fp); |
400 | return (-1); | |
401 | } | |
402 | ||
403 | ptr ++; | |
404 | ||
405 | // Unquote the text... | |
406 | char *sptr, *dptr; // Source/destination pointers | |
407 | ||
408 | for (sptr = ptr, dptr = ptr; *sptr;) | |
ac884b6a | 409 | { |
839a51c8 | 410 | if (*sptr == '\\') |
ac884b6a | 411 | { |
839a51c8 MS |
412 | sptr ++; |
413 | if (isdigit(*sptr)) | |
414 | { | |
415 | *dptr = 0; | |
416 | ||
417 | while (isdigit(*sptr)) | |
418 | { | |
419 | *dptr = *dptr * 8 + *sptr - '0'; | |
420 | sptr ++; | |
421 | } | |
ac884b6a | 422 | |
839a51c8 MS |
423 | dptr ++; |
424 | } | |
425 | else | |
ac884b6a | 426 | { |
839a51c8 MS |
427 | if (*sptr == 'n') |
428 | *dptr++ = '\n'; | |
429 | else if (*sptr == 'r') | |
430 | *dptr++ = '\r'; | |
431 | else if (*sptr == 't') | |
432 | *dptr++ = '\t'; | |
433 | else | |
434 | *dptr++ = *sptr; | |
435 | ||
ac884b6a MS |
436 | sptr ++; |
437 | } | |
ac884b6a MS |
438 | } |
439 | else | |
839a51c8 | 440 | *dptr++ = *sptr++; |
ac884b6a | 441 | } |
ac884b6a | 442 | |
839a51c8 | 443 | *dptr = '\0'; |
ac884b6a | 444 | |
839a51c8 MS |
445 | // Create or add to a message... |
446 | if (!strncmp(line, "msgid", 5)) | |
ac884b6a | 447 | { |
61cf44e2 MS |
448 | if (haveid && havestr) |
449 | add_message(id, str); | |
839a51c8 MS |
450 | |
451 | strlcpy(id, ptr, sizeof(id)); | |
452 | str[0] = '\0'; | |
61cf44e2 MS |
453 | haveid = 1; |
454 | havestr = 0; | |
455 | which = 1; | |
ac884b6a | 456 | } |
839a51c8 MS |
457 | else if (!strncmp(line, "msgstr", 6)) |
458 | { | |
61cf44e2 | 459 | if (!haveid) |
839a51c8 | 460 | { |
61cf44e2 | 461 | _cupsLangPrintf(stderr, |
0837b7e8 MS |
462 | _("ppdc: Need a msgid line before any " |
463 | "translation strings on line %d of %s."), | |
61cf44e2 | 464 | linenum, f); |
839a51c8 MS |
465 | cupsFileClose(fp); |
466 | return (-1); | |
467 | } | |
ac884b6a | 468 | |
839a51c8 | 469 | strlcpy(str, ptr, sizeof(str)); |
61cf44e2 MS |
470 | havestr = 1; |
471 | which = 2; | |
839a51c8 | 472 | } |
61cf44e2 | 473 | else if (line[0] == '\"' && which == 2) |
839a51c8 | 474 | strlcat(str, ptr, sizeof(str)); |
61cf44e2 | 475 | else if (line[0] == '\"' && which == 1) |
839a51c8 MS |
476 | strlcat(id, ptr, sizeof(id)); |
477 | else | |
ac884b6a | 478 | { |
0837b7e8 | 479 | _cupsLangPrintf(stderr, _("ppdc: Unexpected text on line %d of %s."), |
61cf44e2 | 480 | linenum, f); |
ac884b6a MS |
481 | cupsFileClose(fp); |
482 | return (-1); | |
483 | } | |
ac884b6a | 484 | } |
839a51c8 | 485 | |
61cf44e2 MS |
486 | if (haveid && havestr) |
487 | add_message(id, str); | |
ac884b6a | 488 | } |
839a51c8 MS |
489 | else |
490 | goto unknown_load_format; | |
ac884b6a | 491 | |
839a51c8 MS |
492 | /* |
493 | * Close the file and return... | |
494 | */ | |
ac884b6a MS |
495 | |
496 | cupsFileClose(fp); | |
497 | ||
498 | return (0); | |
839a51c8 MS |
499 | |
500 | /* | |
501 | * Unknown format error... | |
502 | */ | |
503 | ||
504 | unknown_load_format: | |
505 | ||
61cf44e2 | 506 | _cupsLangPrintf(stderr, |
0837b7e8 | 507 | _("ppdc: Unknown message catalog format for \"%s\"."), f); |
839a51c8 MS |
508 | cupsFileClose(fp); |
509 | return (-1); | |
ac884b6a MS |
510 | } |
511 | ||
512 | ||
513 | // | |
514 | // 'ppdcCatalog::save_messages()' - Save the messages to a .po file. | |
515 | // | |
516 | ||
517 | int // O - 0 on success, -1 on error | |
518 | ppdcCatalog::save_messages( | |
519 | const char *f) // I - File to save to | |
520 | { | |
521 | cups_file_t *fp; // Message file | |
522 | ppdcMessage *m; // Current message | |
839a51c8 MS |
523 | char *ptr; // Pointer into string |
524 | int utf16; // Output UTF-16 .strings file? | |
525 | int ch; // Current character | |
ac884b6a MS |
526 | |
527 | ||
839a51c8 | 528 | // Open the file... |
ae71f5de | 529 | if ((ptr = (char *)strrchr(f, '.')) == NULL) |
ac884b6a MS |
530 | return (-1); |
531 | ||
839a51c8 MS |
532 | if (!strcmp(ptr, ".gz")) |
533 | fp = cupsFileOpen(f, "w9"); | |
534 | else | |
535 | fp = cupsFileOpen(f, "w"); | |
536 | ||
537 | if (!fp) | |
538 | return (-1); | |
539 | ||
540 | // For .strings files, write a BOM for big-endian output... | |
541 | utf16 = !strcmp(ptr, ".strings"); | |
542 | ||
543 | if (utf16) | |
544 | put_utf16(fp, 0xfeff); | |
545 | ||
546 | // Loop through all of the messages... | |
ac884b6a MS |
547 | for (m = (ppdcMessage *)messages->first(); |
548 | m; | |
549 | m = (ppdcMessage *)messages->next()) | |
550 | { | |
839a51c8 MS |
551 | if (utf16) |
552 | { | |
553 | put_utf16(fp, '\"'); | |
ac884b6a | 554 | |
839a51c8 MS |
555 | ptr = m->id->value; |
556 | while ((ch = get_utf8(ptr)) != 0) | |
557 | switch (ch) | |
558 | { | |
559 | case '\n' : | |
560 | put_utf16(fp, '\\'); | |
561 | put_utf16(fp, 'n'); | |
562 | break; | |
563 | case '\\' : | |
564 | put_utf16(fp, '\\'); | |
565 | put_utf16(fp, '\\'); | |
566 | break; | |
567 | case '\"' : | |
568 | put_utf16(fp, '\\'); | |
569 | put_utf16(fp, '\"'); | |
570 | break; | |
571 | default : | |
572 | put_utf16(fp, ch); | |
573 | break; | |
574 | } | |
575 | ||
576 | put_utf16(fp, '\"'); | |
577 | put_utf16(fp, ' '); | |
578 | put_utf16(fp, '='); | |
579 | put_utf16(fp, ' '); | |
580 | put_utf16(fp, '\"'); | |
581 | ||
582 | ptr = m->string->value; | |
583 | while ((ch = get_utf8(ptr)) != 0) | |
584 | switch (ch) | |
585 | { | |
586 | case '\n' : | |
587 | put_utf16(fp, '\\'); | |
588 | put_utf16(fp, 'n'); | |
589 | break; | |
590 | case '\\' : | |
591 | put_utf16(fp, '\\'); | |
592 | put_utf16(fp, '\\'); | |
593 | break; | |
594 | case '\"' : | |
595 | put_utf16(fp, '\\'); | |
596 | put_utf16(fp, '\"'); | |
597 | break; | |
598 | default : | |
599 | put_utf16(fp, ch); | |
600 | break; | |
601 | } | |
ac884b6a | 602 | |
839a51c8 MS |
603 | put_utf16(fp, '\"'); |
604 | put_utf16(fp, ';'); | |
605 | put_utf16(fp, '\n'); | |
606 | } | |
607 | else | |
608 | { | |
609 | cupsFilePuts(fp, "msgid \""); | |
610 | for (ptr = m->id->value; *ptr; ptr ++) | |
611 | switch (*ptr) | |
612 | { | |
613 | case '\n' : | |
614 | cupsFilePuts(fp, "\\n"); | |
615 | break; | |
616 | case '\\' : | |
617 | cupsFilePuts(fp, "\\\\"); | |
618 | break; | |
619 | case '\"' : | |
620 | cupsFilePuts(fp, "\\\""); | |
621 | break; | |
622 | default : | |
623 | cupsFilePutChar(fp, *ptr); | |
624 | break; | |
625 | } | |
626 | cupsFilePuts(fp, "\"\n"); | |
627 | ||
628 | cupsFilePuts(fp, "msgstr \""); | |
629 | for (ptr = m->string->value; *ptr; ptr ++) | |
630 | switch (*ptr) | |
631 | { | |
632 | case '\n' : | |
633 | cupsFilePuts(fp, "\\n"); | |
634 | break; | |
635 | case '\\' : | |
636 | cupsFilePuts(fp, "\\\\"); | |
637 | break; | |
638 | case '\"' : | |
639 | cupsFilePuts(fp, "\\\""); | |
640 | break; | |
641 | default : | |
642 | cupsFilePutChar(fp, *ptr); | |
643 | break; | |
644 | } | |
645 | cupsFilePuts(fp, "\"\n"); | |
646 | ||
647 | cupsFilePutChar(fp, '\n'); | |
648 | } | |
ac884b6a MS |
649 | } |
650 | ||
651 | cupsFileClose(fp); | |
652 | ||
653 | return (0); | |
654 | } | |
655 | ||
656 | ||
be76a973 MS |
657 | #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR) |
658 | // | |
659 | // 'apple_add_message()' - Add a message from a localization dictionary. | |
660 | // | |
661 | ||
662 | static void | |
663 | apple_add_message(CFStringRef key, // I - Localization key | |
664 | CFStringRef val, // I - Localized value | |
665 | ppdcCatalog *c) // I - Message catalog | |
666 | { | |
667 | char id[1024], // Message id | |
668 | str[1024]; // Localized message | |
669 | ||
670 | ||
671 | if (CFStringGetCString(key, id, sizeof(id), kCFStringEncodingUTF8) && | |
672 | CFStringGetCString(val, str, sizeof(str), kCFStringEncodingUTF8)) | |
673 | c->add_message(id, str); | |
674 | } | |
675 | #endif /* __APPLE__ && CUPS_BUNDLEDIR */ | |
676 | ||
677 | ||
839a51c8 MS |
678 | // |
679 | // 'get_utf8()' - Get a UTF-8 character. | |
680 | // | |
681 | ||
682 | static int // O - Unicode character or 0 on EOF | |
683 | get_utf8(char *&ptr) // IO - Pointer to character | |
684 | { | |
685 | int ch; // Current character | |
686 | ||
687 | ||
688 | if ((ch = *ptr++ & 255) < 0xc0) | |
689 | return (ch); | |
690 | ||
691 | if ((ch & 0xe0) == 0xc0) | |
692 | { | |
693 | // Two-byte UTF-8... | |
694 | if ((*ptr & 0xc0) != 0x80) | |
695 | return (0); | |
696 | ||
697 | ch = ((ch & 0x1f) << 6) | (*ptr++ & 0x3f); | |
698 | } | |
699 | else if ((ch & 0xf0) == 0xe0) | |
700 | { | |
701 | // Three-byte UTF-8... | |
702 | if ((*ptr & 0xc0) != 0x80) | |
703 | return (0); | |
704 | ||
705 | ch = ((ch & 0x0f) << 6) | (*ptr++ & 0x3f); | |
706 | ||
707 | if ((*ptr & 0xc0) != 0x80) | |
708 | return (0); | |
709 | ||
710 | ch = (ch << 6) | (*ptr++ & 0x3f); | |
711 | } | |
712 | else if ((ch & 0xf8) == 0xf0) | |
713 | { | |
714 | // Four-byte UTF-8... | |
715 | if ((*ptr & 0xc0) != 0x80) | |
716 | return (0); | |
717 | ||
718 | ch = ((ch & 0x07) << 6) | (*ptr++ & 0x3f); | |
719 | ||
720 | if ((*ptr & 0xc0) != 0x80) | |
721 | return (0); | |
722 | ||
723 | ch = (ch << 6) | (*ptr++ & 0x3f); | |
724 | ||
725 | if ((*ptr & 0xc0) != 0x80) | |
726 | return (0); | |
727 | ||
728 | ch = (ch << 6) | (*ptr++ & 0x3f); | |
729 | } | |
730 | ||
731 | return (ch); | |
732 | } | |
733 | ||
734 | ||
735 | // | |
736 | // 'get_utf16()' - Get a UTF-16 character... | |
737 | // | |
738 | ||
739 | static int // O - Unicode character or 0 on EOF | |
740 | get_utf16(cups_file_t *fp, // I - File to read from | |
741 | ppdc_cs_t &cs) // IO - Character set of file | |
742 | { | |
743 | int ch; // Current character | |
744 | unsigned char buffer[3]; // Bytes | |
745 | ||
746 | ||
747 | if (cs == PPDC_CS_AUTO) | |
748 | { | |
749 | // Get byte-order-mark, if present... | |
750 | if (cupsFileRead(fp, (char *)buffer, 2) != 2) | |
751 | return (0); | |
752 | ||
753 | if (buffer[0] == 0xfe && buffer[1] == 0xff) | |
754 | { | |
755 | // Big-endian UTF-16... | |
756 | cs = PPDC_CS_UTF16BE; | |
757 | ||
758 | if (cupsFileRead(fp, (char *)buffer, 2) != 2) | |
759 | return (0); | |
760 | } | |
761 | else if (buffer[0] == 0xff && buffer[1] == 0xfe) | |
762 | { | |
763 | // Little-endian UTF-16... | |
764 | cs = PPDC_CS_UTF16LE; | |
765 | ||
766 | if (cupsFileRead(fp, (char *)buffer, 2) != 2) | |
767 | return (0); | |
768 | } | |
769 | else if (buffer[0] == 0x00 && buffer[1] != 0x00) | |
770 | { | |
771 | // No BOM, assume big-endian UTF-16... | |
772 | cs = PPDC_CS_UTF16BE; | |
773 | } | |
774 | else if (buffer[0] != 0x00 && buffer[1] == 0x00) | |
775 | { | |
776 | // No BOM, assume little-endian UTF-16... | |
777 | cs = PPDC_CS_UTF16LE; | |
778 | } | |
779 | else | |
780 | { | |
781 | // No BOM, assume UTF-8... | |
782 | cs = PPDC_CS_UTF8; | |
783 | ||
784 | cupsFileRewind(fp); | |
785 | } | |
786 | } | |
787 | else if (cs != PPDC_CS_UTF8) | |
788 | { | |
789 | if (cupsFileRead(fp, (char *)buffer, 2) != 2) | |
790 | return (0); | |
791 | } | |
792 | ||
793 | if (cs == PPDC_CS_UTF8) | |
794 | { | |
795 | // UTF-8 character... | |
4509bb49 MS |
796 | if ((ch = cupsFileGetChar(fp)) < 0) |
797 | return (0); | |
839a51c8 MS |
798 | |
799 | if ((ch & 0xe0) == 0xc0) | |
800 | { | |
801 | // Two-byte UTF-8... | |
802 | if (cupsFileRead(fp, (char *)buffer, 1) != 1) | |
803 | return (0); | |
804 | ||
805 | if ((buffer[0] & 0xc0) != 0x80) | |
806 | return (0); | |
807 | ||
808 | ch = ((ch & 0x1f) << 6) | (buffer[0] & 0x3f); | |
809 | } | |
810 | else if ((ch & 0xf0) == 0xe0) | |
811 | { | |
812 | // Three-byte UTF-8... | |
813 | if (cupsFileRead(fp, (char *)buffer, 2) != 2) | |
814 | return (0); | |
815 | ||
816 | if ((buffer[0] & 0xc0) != 0x80 || | |
817 | (buffer[1] & 0xc0) != 0x80) | |
818 | return (0); | |
819 | ||
820 | ch = ((((ch & 0x0f) << 6) | (buffer[0] & 0x3f)) << 6) | | |
821 | (buffer[1] & 0x3f); | |
822 | } | |
823 | else if ((ch & 0xf8) == 0xf0) | |
824 | { | |
825 | // Four-byte UTF-8... | |
826 | if (cupsFileRead(fp, (char *)buffer, 3) != 3) | |
827 | return (0); | |
828 | ||
829 | if ((buffer[0] & 0xc0) != 0x80 || | |
830 | (buffer[1] & 0xc0) != 0x80 || | |
831 | (buffer[2] & 0xc0) != 0x80) | |
832 | return (0); | |
833 | ||
834 | ch = ((((((ch & 0x07) << 6) | (buffer[0] & 0x3f)) << 6) | | |
835 | (buffer[1] & 0x3f)) << 6) | (buffer[2] & 0x3f); | |
836 | } | |
837 | } | |
838 | else | |
839 | { | |
840 | // UTF-16 character... | |
841 | if (cs == PPDC_CS_UTF16BE) | |
842 | ch = (buffer[0] << 8) | buffer[1]; | |
843 | else | |
844 | ch = (buffer[1] << 8) | buffer[0]; | |
845 | ||
846 | if (ch >= 0xd800 && ch <= 0xdbff) | |
847 | { | |
848 | // Handle multi-word encoding... | |
849 | int lch; | |
850 | ||
851 | if (cupsFileRead(fp, (char *)buffer, 2) != 2) | |
852 | return (0); | |
853 | ||
854 | if (cs == PPDC_CS_UTF16BE) | |
855 | lch = (buffer[0] << 8) | buffer[1]; | |
856 | else | |
857 | lch = (buffer[1] << 8) | buffer[0]; | |
858 | ||
859 | if (lch < 0xdc00 || lch >= 0xdfff) | |
860 | return (0); | |
861 | ||
862 | ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000; | |
863 | } | |
864 | } | |
865 | ||
866 | return (ch); | |
867 | } | |
868 | ||
869 | ||
870 | // | |
871 | // 'put_utf8()' - Add a UTF-8 character to a string. | |
872 | // | |
873 | ||
874 | static int // O - 0 on success, -1 on failure | |
875 | put_utf8(int ch, // I - Unicode character | |
876 | char *&ptr, // IO - String pointer | |
877 | char *end) // I - End of buffer | |
878 | { | |
879 | if (ch < 0x80) | |
880 | { | |
881 | // One-byte ASCII... | |
882 | if (ptr >= end) | |
883 | return (-1); | |
884 | ||
7e86f2f6 | 885 | *ptr++ = (char)ch; |
839a51c8 MS |
886 | } |
887 | else if (ch < 0x800) | |
888 | { | |
889 | // Two-byte UTF-8... | |
890 | if ((ptr + 1) >= end) | |
891 | return (-1); | |
892 | ||
7e86f2f6 MS |
893 | *ptr++ = (char)(0xc0 | (ch >> 6)); |
894 | *ptr++ = (char)(0x80 | (ch & 0x3f)); | |
839a51c8 MS |
895 | } |
896 | else if (ch < 0x10000) | |
897 | { | |
898 | // Three-byte UTF-8... | |
899 | if ((ptr + 2) >= end) | |
900 | return (-1); | |
901 | ||
7e86f2f6 MS |
902 | *ptr++ = (char)(0xe0 | (ch >> 12)); |
903 | *ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f)); | |
904 | *ptr++ = (char)(0x80 | (ch & 0x3f)); | |
839a51c8 MS |
905 | } |
906 | else | |
907 | { | |
908 | // Four-byte UTF-8... | |
909 | if ((ptr + 3) >= end) | |
910 | return (-1); | |
911 | ||
7e86f2f6 MS |
912 | *ptr++ = (char)(0xf0 | (ch >> 18)); |
913 | *ptr++ = (char)(0x80 | ((ch >> 12) & 0x3f)); | |
914 | *ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f)); | |
915 | *ptr++ = (char)(0x80 | (ch & 0x3f)); | |
839a51c8 MS |
916 | } |
917 | ||
918 | return (0); | |
919 | } | |
920 | ||
921 | ||
922 | // | |
923 | // 'put_utf16()' - Write a UTF-16 character to a file. | |
924 | // | |
925 | ||
926 | static int // O - 0 on success, -1 on failure | |
927 | put_utf16(cups_file_t *fp, // I - File to write to | |
928 | int ch) // I - Unicode character | |
929 | { | |
930 | unsigned char buffer[4]; // Output buffer | |
931 | ||
932 | ||
933 | if (ch < 0x10000) | |
934 | { | |
935 | // One-word UTF-16 big-endian... | |
7e86f2f6 MS |
936 | buffer[0] = (unsigned char)(ch >> 8); |
937 | buffer[1] = (unsigned char)ch; | |
839a51c8 MS |
938 | |
939 | if (cupsFileWrite(fp, (char *)buffer, 2) == 2) | |
940 | return (0); | |
941 | } | |
942 | else | |
943 | { | |
944 | // Two-word UTF-16 big-endian... | |
945 | ch -= 0x10000; | |
946 | ||
7e86f2f6 MS |
947 | buffer[0] = (unsigned char)(0xd8 | (ch >> 18)); |
948 | buffer[1] = (unsigned char)(ch >> 10); | |
949 | buffer[2] = (unsigned char)(0xdc | ((ch >> 8) & 0x03)); | |
950 | buffer[3] = (unsigned char)ch; | |
839a51c8 MS |
951 | |
952 | if (cupsFileWrite(fp, (char *)buffer, 4) == 4) | |
953 | return (0); | |
954 | } | |
955 | ||
956 | return (-1); | |
957 | } |