2 // Shared message catalog class for the CUPS PPD Compiler.
4 // Copyright 2007-2016 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 // file is 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();
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)