2 // Shared message catalog class for the CUPS PPD Compiler.
4 // Copyright 2007-2017 by Apple Inc.
5 // Copyright 2002-2006 by Easy Software Products.
7 // These coded instructions, statements, and computer programs are the
8 // property of Apple Inc. and are protected by Federal copyright
9 // law. Distribution and use rights are outlined in the file "LICENSE.txt"
10 // which should have been included with this file. If this file is
11 // missing or damaged, see the license at "http://www.cups.org/".
15 // Include necessary headers...
18 #include "ppdc-private.h"
22 // Character encodings...
38 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
39 static void apple_add_message(CFStringRef key, CFStringRef val, ppdcCatalog *c);
40 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
41 static int get_utf8(char *&ptr);
42 static int get_utf16(cups_file_t *fp, ppdc_cs_t &cs);
43 static int put_utf8(int ch, char *&ptr, char *end);
44 static int put_utf16(cups_file_t *fp, int ch);
48 // 'ppdcCatalog::ppdcCatalog()' - Create a shared message catalog.
51 ppdcCatalog::ppdcCatalog(const char *l, // I - Locale
52 const char *f) // I - Message catalog file
57 locale = new ppdcString(l);
58 filename = new ppdcString(f);
59 messages = new ppdcArray();
61 if (l && strcmp(l, "en"))
63 // Try loading the base messages for this locale...
64 char pofile[1024]; // Message catalog file
67 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
68 char applelang[256]; // Apple language ID
69 CFURLRef url; // URL to cups.strings file
70 CFReadStreamRef stream = NULL; // File stream
71 CFPropertyListRef plist = NULL; // Localization file
73 snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", _cupsAppleLanguage(l, applelang, sizeof(applelang)));
74 if (access(pofile, 0))
76 // Try alternate lproj directory names...
77 const char *tl = l; // Temporary locale string
79 if (!strncmp(l, "en", 2))
81 else if (!strncmp(l, "nb", 2))
83 else if (!strncmp(l, "nl", 2))
85 else if (!strncmp(l, "fr", 2))
87 else if (!strncmp(l, "de", 2))
89 else if (!strncmp(l, "it", 2))
91 else if (!strncmp(l, "ja", 2))
93 else if (!strncmp(l, "es", 2))
96 snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", tl);
99 url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8 *)pofile, (CFIndex)strlen(pofile), false);
102 stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
107 * Read the property list containing the localization data.
110 CFReadStreamOpen(stream);
112 plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, NULL, NULL);
114 if (plist && CFGetTypeID(plist) == CFDictionaryGetTypeID())
115 CFDictionaryApplyFunction((CFDictionaryRef)plist, (CFDictionaryApplierFunction)apple_add_message, this);
127 _cups_globals_t *cg = _cupsGlobals();
128 // Global information
130 snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir, l, l);
132 if (load_messages(pofile) && strchr(l, '_'))
134 // Try the base locale...
135 char baseloc[3]; // Base locale...
138 strlcpy(baseloc, l, sizeof(baseloc));
139 snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir,
142 load_messages(pofile);
144 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
153 // 'ppdcCatalog::~ppdcCatalog()' - Destroy a shared message catalog.
156 ppdcCatalog::~ppdcCatalog()
167 // 'ppdcCatalog::add_message()' - Add a new message.
171 ppdcCatalog::add_message(
172 const char *id, // I - Message ID to add
173 const char *string) // I - Translation string
175 ppdcMessage *m; // Current message
176 char text[1024]; // Text to translate
179 // Range check input...
183 // Verify that we don't already have the message ID...
184 for (m = (ppdcMessage *)messages->first();
186 m = (ppdcMessage *)messages->next())
187 if (!strcmp(m->id->value, id))
191 m->string->release();
192 m->string = new ppdcString(string);
197 // Add the message...
200 snprintf(text, sizeof(text), "TRANSLATE %s", id);
204 messages->add(new ppdcMessage(id, string));
209 // 'ppdcCatalog::find_message()' - Find a message in a catalog...
212 const char * // O - Message text
213 ppdcCatalog::find_message(
214 const char *id) // I - Message ID
216 ppdcMessage *m; // Current message
222 for (m = (ppdcMessage *)messages->first();
224 m = (ppdcMessage *)messages->next())
225 if (!strcmp(m->id->value, id))
226 return (m->string->value);
233 // 'ppdcCatalog::load_messages()' - Load messages from a .po file.
236 int // O - 0 on success, -1 on failure
237 ppdcCatalog::load_messages(
238 const char *f) // I - Message catalog file
240 cups_file_t *fp; // Message file
241 char line[4096], // Line buffer
242 *ptr, // Pointer into buffer
243 id[4096], // Translation ID
244 str[4096]; // Translation string
245 int linenum; // Line number
248 // Open the message catalog file...
249 if ((fp = cupsFileOpen(f, "r")) == NULL)
252 if ((ptr = (char *)strrchr(f, '.')) == NULL)
253 goto unknown_load_format;
254 else if (!strcmp(ptr, ".strings"))
257 * Read messages in macOS ".strings" format, which are either UTF-8/UTF-16
258 * text files of the format:
262 * Strings files can also contain C-style comments.
265 ppdc_cs_t cs = PPDC_CS_AUTO; // Character set for file
266 int ch; // Current character from file
267 char *end; // End of buffer
275 while ((ch = get_utf16(fp, cs)) != 0)
281 if ((ch = get_utf16(fp, cs)) == 0)
296 put_utf8(ch, ptr, end);
300 // Start of a comment?
301 if ((ch = get_utf16(fp, cs)) == 0)
309 while ((ch = get_utf16(fp, cs)) != 0)
311 if (ch == '/' && lastch == '*')
319 // Skip C++ comment...
320 while ((ch = get_utf16(fp, cs)) != 0)
327 // Start quoted string...
331 end = str + sizeof(str) - 1;
336 end = id + sizeof(id) - 1;
342 add_message(id, str);
347 else if (!strcmp(ptr, ".po") || !strcmp(ptr, ".gz"))
350 * Read messages from the catalog file until EOF...
352 * The format is the GNU gettext .po format, which is fairly simple:
355 * msgstr "localized text"
357 * The ID and localized text can span multiple lines using the form:
362 * "localized text spanning "
366 int which, // In msgid?
367 haveid, // Did we get a msgid string?
368 havestr; // Did we get a msgstr string?
377 while (cupsFileGets(fp, line, sizeof(line)))
381 // Skip blank and comment lines...
382 if (line[0] == '#' || !line[0])
385 // Strip the trailing quote...
386 if ((ptr = (char *)strrchr(line, '\"')) == NULL)
388 _cupsLangPrintf(stderr,
389 _("ppdc: Expected quoted string on line %d of %s."),
397 // Find start of value...
398 if ((ptr = strchr(line, '\"')) == NULL)
400 _cupsLangPrintf(stderr,
401 _("ppdc: Expected quoted string on line %d of %s."),
409 // Unquote the text...
410 char *sptr, *dptr; // Source/destination pointers
412 for (sptr = ptr, dptr = ptr; *sptr;)
421 while (isdigit(*sptr))
423 *dptr = *dptr * 8 + *sptr - '0';
433 else if (*sptr == 'r')
435 else if (*sptr == 't')
449 // Create or add to a message...
450 if (!strncmp(line, "msgid", 5))
452 if (haveid && havestr)
453 add_message(id, str);
455 strlcpy(id, ptr, sizeof(id));
461 else if (!strncmp(line, "msgstr", 6))
465 _cupsLangPrintf(stderr,
466 _("ppdc: Need a msgid line before any "
467 "translation strings on line %d of %s."),
473 strlcpy(str, ptr, sizeof(str));
477 else if (line[0] == '\"' && which == 2)
478 strlcat(str, ptr, sizeof(str));
479 else if (line[0] == '\"' && which == 1)
480 strlcat(id, ptr, sizeof(id));
483 _cupsLangPrintf(stderr, _("ppdc: Unexpected text on line %d of %s."),
490 if (haveid && havestr)
491 add_message(id, str);
494 goto unknown_load_format;
497 * Close the file and return...
505 * Unknown format error...
510 _cupsLangPrintf(stderr,
511 _("ppdc: Unknown message catalog format for \"%s\"."), f);
518 // 'ppdcCatalog::save_messages()' - Save the messages to a .po file.
521 int // O - 0 on success, -1 on error
522 ppdcCatalog::save_messages(
523 const char *f) // I - File to save to
525 cups_file_t *fp; // Message file
526 ppdcMessage *m; // Current message
527 char *ptr; // Pointer into string
528 int utf16; // Output UTF-16 .strings file?
529 int ch; // Current character
533 if ((ptr = (char *)strrchr(f, '.')) == NULL)
536 if (!strcmp(ptr, ".gz"))
537 fp = cupsFileOpen(f, "w9");
539 fp = cupsFileOpen(f, "w");
544 // For .strings files, write a BOM for big-endian output...
545 utf16 = !strcmp(ptr, ".strings");
548 put_utf16(fp, 0xfeff);
550 // Loop through all of the messages...
551 for (m = (ppdcMessage *)messages->first();
553 m = (ppdcMessage *)messages->next())
560 while ((ch = get_utf8(ptr)) != 0)
586 ptr = m->string->value;
587 while ((ch = get_utf8(ptr)) != 0)
613 cupsFilePuts(fp, "msgid \"");
614 for (ptr = m->id->value; *ptr; ptr ++)
618 cupsFilePuts(fp, "\\n");
621 cupsFilePuts(fp, "\\\\");
624 cupsFilePuts(fp, "\\\"");
627 cupsFilePutChar(fp, *ptr);
630 cupsFilePuts(fp, "\"\n");
632 cupsFilePuts(fp, "msgstr \"");
633 for (ptr = m->string->value; *ptr; ptr ++)
637 cupsFilePuts(fp, "\\n");
640 cupsFilePuts(fp, "\\\\");
643 cupsFilePuts(fp, "\\\"");
646 cupsFilePutChar(fp, *ptr);
649 cupsFilePuts(fp, "\"\n");
651 cupsFilePutChar(fp, '\n');
661 #if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
663 // 'apple_add_message()' - Add a message from a localization dictionary.
667 apple_add_message(CFStringRef key, // I - Localization key
668 CFStringRef val, // I - Localized value
669 ppdcCatalog *c) // I - Message catalog
671 char id[1024], // Message id
672 str[1024]; // Localized message
675 if (CFStringGetCString(key, id, sizeof(id), kCFStringEncodingUTF8) &&
676 CFStringGetCString(val, str, sizeof(str), kCFStringEncodingUTF8))
677 c->add_message(id, str);
679 #endif /* __APPLE__ && CUPS_BUNDLEDIR */
683 // 'get_utf8()' - Get a UTF-8 character.
686 static int // O - Unicode character or 0 on EOF
687 get_utf8(char *&ptr) // IO - Pointer to character
689 int ch; // Current character
692 if ((ch = *ptr++ & 255) < 0xc0)
695 if ((ch & 0xe0) == 0xc0)
698 if ((*ptr & 0xc0) != 0x80)
701 ch = ((ch & 0x1f) << 6) | (*ptr++ & 0x3f);
703 else if ((ch & 0xf0) == 0xe0)
705 // Three-byte UTF-8...
706 if ((*ptr & 0xc0) != 0x80)
709 ch = ((ch & 0x0f) << 6) | (*ptr++ & 0x3f);
711 if ((*ptr & 0xc0) != 0x80)
714 ch = (ch << 6) | (*ptr++ & 0x3f);
716 else if ((ch & 0xf8) == 0xf0)
718 // Four-byte UTF-8...
719 if ((*ptr & 0xc0) != 0x80)
722 ch = ((ch & 0x07) << 6) | (*ptr++ & 0x3f);
724 if ((*ptr & 0xc0) != 0x80)
727 ch = (ch << 6) | (*ptr++ & 0x3f);
729 if ((*ptr & 0xc0) != 0x80)
732 ch = (ch << 6) | (*ptr++ & 0x3f);
740 // 'get_utf16()' - Get a UTF-16 character...
743 static int // O - Unicode character or 0 on EOF
744 get_utf16(cups_file_t *fp, // I - File to read from
745 ppdc_cs_t &cs) // IO - Character set of file
747 int ch; // Current character
748 unsigned char buffer[3]; // Bytes
751 if (cs == PPDC_CS_AUTO)
753 // Get byte-order-mark, if present...
754 if (cupsFileRead(fp, (char *)buffer, 2) != 2)
757 if (buffer[0] == 0xfe && buffer[1] == 0xff)
759 // Big-endian UTF-16...
760 cs = PPDC_CS_UTF16BE;
762 if (cupsFileRead(fp, (char *)buffer, 2) != 2)
765 else if (buffer[0] == 0xff && buffer[1] == 0xfe)
767 // Little-endian UTF-16...
768 cs = PPDC_CS_UTF16LE;
770 if (cupsFileRead(fp, (char *)buffer, 2) != 2)
773 else if (buffer[0] == 0x00 && buffer[1] != 0x00)
775 // No BOM, assume big-endian UTF-16...
776 cs = PPDC_CS_UTF16BE;
778 else if (buffer[0] != 0x00 && buffer[1] == 0x00)
780 // No BOM, assume little-endian UTF-16...
781 cs = PPDC_CS_UTF16LE;
785 // No BOM, assume UTF-8...
791 else if (cs != PPDC_CS_UTF8)
793 if (cupsFileRead(fp, (char *)buffer, 2) != 2)
797 if (cs == PPDC_CS_UTF8)
799 // UTF-8 character...
800 if ((ch = cupsFileGetChar(fp)) < 0)
803 if ((ch & 0xe0) == 0xc0)
806 if (cupsFileRead(fp, (char *)buffer, 1) != 1)
809 if ((buffer[0] & 0xc0) != 0x80)
812 ch = ((ch & 0x1f) << 6) | (buffer[0] & 0x3f);
814 else if ((ch & 0xf0) == 0xe0)
816 // Three-byte UTF-8...
817 if (cupsFileRead(fp, (char *)buffer, 2) != 2)
820 if ((buffer[0] & 0xc0) != 0x80 ||
821 (buffer[1] & 0xc0) != 0x80)
824 ch = ((((ch & 0x0f) << 6) | (buffer[0] & 0x3f)) << 6) |
827 else if ((ch & 0xf8) == 0xf0)
829 // Four-byte UTF-8...
830 if (cupsFileRead(fp, (char *)buffer, 3) != 3)
833 if ((buffer[0] & 0xc0) != 0x80 ||
834 (buffer[1] & 0xc0) != 0x80 ||
835 (buffer[2] & 0xc0) != 0x80)
838 ch = ((((((ch & 0x07) << 6) | (buffer[0] & 0x3f)) << 6) |
839 (buffer[1] & 0x3f)) << 6) | (buffer[2] & 0x3f);
844 // UTF-16 character...
845 if (cs == PPDC_CS_UTF16BE)
846 ch = (buffer[0] << 8) | buffer[1];
848 ch = (buffer[1] << 8) | buffer[0];
850 if (ch >= 0xd800 && ch <= 0xdbff)
852 // Handle multi-word encoding...
855 if (cupsFileRead(fp, (char *)buffer, 2) != 2)
858 if (cs == PPDC_CS_UTF16BE)
859 lch = (buffer[0] << 8) | buffer[1];
861 lch = (buffer[1] << 8) | buffer[0];
863 if (lch < 0xdc00 || lch >= 0xdfff)
866 ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000;
875 // 'put_utf8()' - Add a UTF-8 character to a string.
878 static int // O - 0 on success, -1 on failure
879 put_utf8(int ch, // I - Unicode character
880 char *&ptr, // IO - String pointer
881 char *end) // I - End of buffer
894 if ((ptr + 1) >= end)
897 *ptr++ = (char)(0xc0 | (ch >> 6));
898 *ptr++ = (char)(0x80 | (ch & 0x3f));
900 else if (ch < 0x10000)
902 // Three-byte UTF-8...
903 if ((ptr + 2) >= end)
906 *ptr++ = (char)(0xe0 | (ch >> 12));
907 *ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
908 *ptr++ = (char)(0x80 | (ch & 0x3f));
912 // Four-byte UTF-8...
913 if ((ptr + 3) >= end)
916 *ptr++ = (char)(0xf0 | (ch >> 18));
917 *ptr++ = (char)(0x80 | ((ch >> 12) & 0x3f));
918 *ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
919 *ptr++ = (char)(0x80 | (ch & 0x3f));
927 // 'put_utf16()' - Write a UTF-16 character to a file.
930 static int // O - 0 on success, -1 on failure
931 put_utf16(cups_file_t *fp, // I - File to write to
932 int ch) // I - Unicode character
934 unsigned char buffer[4]; // Output buffer
939 // One-word UTF-16 big-endian...
940 buffer[0] = (unsigned char)(ch >> 8);
941 buffer[1] = (unsigned char)ch;
943 if (cupsFileWrite(fp, (char *)buffer, 2) == 2)
948 // Two-word UTF-16 big-endian...
951 buffer[0] = (unsigned char)(0xd8 | (ch >> 18));
952 buffer[1] = (unsigned char)(ch >> 10);
953 buffer[2] = (unsigned char)(0xdc | ((ch >> 8) & 0x03));
954 buffer[3] = (unsigned char)ch;
956 if (cupsFileWrite(fp, (char *)buffer, 4) == 4)