blob: 4aac366260a08d7ad71f6bcb18ed1a8955177a51 [file] [log] [blame]
//
// Shared message catalog class for the CUPS PPD Compiler.
//
// Copyright 2007-2017 by Apple Inc.
// Copyright 2002-2006 by Easy Software Products.
//
// Licensed under Apache License v2.0. See the file "LICENSE" for more information.
//
//
// Include necessary headers...
//
#include "ppdc-private.h"
//
// Character encodings...
//
typedef enum
{
PPDC_CS_AUTO,
PPDC_CS_UTF8,
PPDC_CS_UTF16BE,
PPDC_CS_UTF16LE
} ppdc_cs_t;
//
// Local functions...
//
#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
static void apple_add_message(CFStringRef key, CFStringRef val, ppdcCatalog *c);
#endif /* __APPLE__ && CUPS_BUNDLEDIR */
static int get_utf8(char *&ptr);
static int get_utf16(cups_file_t *fp, ppdc_cs_t &cs);
static int put_utf8(int ch, char *&ptr, char *end);
static int put_utf16(cups_file_t *fp, int ch);
//
// 'ppdcCatalog::ppdcCatalog()' - Create a shared message catalog.
//
ppdcCatalog::ppdcCatalog(const char *l, // I - Locale
const char *f) // I - Message catalog file
: ppdcShared()
{
PPDC_NEW;
locale = new ppdcString(l);
filename = new ppdcString(f);
messages = new ppdcArray();
if (l && strcmp(l, "en"))
{
// Try loading the base messages for this locale...
char pofile[1024]; // Message catalog file
#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
char applelang[256]; // Apple language ID
CFURLRef url; // URL to cups.strings file
CFReadStreamRef stream = NULL; // File stream
CFPropertyListRef plist = NULL; // Localization file
snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", _cupsAppleLanguage(l, applelang, sizeof(applelang)));
if (access(pofile, 0))
{
// Try alternate lproj directory names...
const char *tl = l; // Temporary locale string
if (!strncmp(l, "en", 2))
tl = "English";
else if (!strncmp(l, "nb", 2))
tl = "no";
else if (!strncmp(l, "nl", 2))
tl = "Dutch";
else if (!strncmp(l, "fr", 2))
tl = "French";
else if (!strncmp(l, "de", 2))
tl = "German";
else if (!strncmp(l, "it", 2))
tl = "Italian";
else if (!strncmp(l, "ja", 2))
tl = "Japanese";
else if (!strncmp(l, "es", 2))
tl = "Spanish";
snprintf(pofile, sizeof(pofile), CUPS_BUNDLEDIR "/Resources/%s.lproj/cups.strings", tl);
}
url = CFURLCreateFromFileSystemRepresentation(kCFAllocatorDefault, (UInt8 *)pofile, (CFIndex)strlen(pofile), false);
if (url)
{
stream = CFReadStreamCreateWithFile(kCFAllocatorDefault, url);
if (stream)
{
/*
* Read the property list containing the localization data.
*/
CFReadStreamOpen(stream);
plist = CFPropertyListCreateWithStream(kCFAllocatorDefault, stream, 0, kCFPropertyListImmutable, NULL, NULL);
if (plist && CFGetTypeID(plist) == CFDictionaryGetTypeID())
CFDictionaryApplyFunction((CFDictionaryRef)plist, (CFDictionaryApplierFunction)apple_add_message, this);
if (plist)
CFRelease(plist);
CFRelease(stream);
}
CFRelease(url);
}
#else
_cups_globals_t *cg = _cupsGlobals();
// Global information
snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir, l, l);
if (load_messages(pofile) && strchr(l, '_'))
{
// Try the base locale...
char baseloc[3]; // Base locale...
strlcpy(baseloc, l, sizeof(baseloc));
snprintf(pofile, sizeof(pofile), "%s/%s/cups_%s.po", cg->localedir,
baseloc, baseloc);
load_messages(pofile);
}
#endif /* __APPLE__ && CUPS_BUNDLEDIR */
}
if (f && *f)
load_messages(f);
}
//
// 'ppdcCatalog::~ppdcCatalog()' - Destroy a shared message catalog.
//
ppdcCatalog::~ppdcCatalog()
{
PPDC_DELETE;
locale->release();
filename->release();
messages->release();
}
//
// 'ppdcCatalog::add_message()' - Add a new message.
//
void
ppdcCatalog::add_message(
const char *id, // I - Message ID to add
const char *string) // I - Translation string
{
ppdcMessage *m; // Current message
char text[1024]; // Text to translate
// Range check input...
if (!id)
return;
// Verify that we don't already have the message ID...
for (m = (ppdcMessage *)messages->first();
m;
m = (ppdcMessage *)messages->next())
if (!strcmp(m->id->value, id))
{
if (string)
{
m->string->release();
m->string = new ppdcString(string);
}
return;
}
// Add the message...
if (!string)
{
snprintf(text, sizeof(text), "TRANSLATE %s", id);
string = text;
}
messages->add(new ppdcMessage(id, string));
}
//
// 'ppdcCatalog::find_message()' - Find a message in a catalog...
//
const char * // O - Message text
ppdcCatalog::find_message(
const char *id) // I - Message ID
{
ppdcMessage *m; // Current message
if (!*id)
return (id);
for (m = (ppdcMessage *)messages->first();
m;
m = (ppdcMessage *)messages->next())
if (!strcmp(m->id->value, id))
return (m->string->value);
return (id);
}
//
// 'ppdcCatalog::load_messages()' - Load messages from a .po file.
//
int // O - 0 on success, -1 on failure
ppdcCatalog::load_messages(
const char *f) // I - Message catalog file
{
cups_file_t *fp; // Message file
char line[4096], // Line buffer
*ptr, // Pointer into buffer
id[4096], // Translation ID
str[4096]; // Translation string
int linenum; // Line number
// Open the message catalog file...
if ((fp = cupsFileOpen(f, "r")) == NULL)
return (-1);
if ((ptr = (char *)strrchr(f, '.')) == NULL)
goto unknown_load_format;
else if (!strcmp(ptr, ".strings"))
{
/*
* Read messages in macOS ".strings" format, which are either UTF-8/UTF-16
* text files of the format:
*
* "id" = "str";
*
* Strings files can also contain C-style comments.
*/
ppdc_cs_t cs = PPDC_CS_AUTO; // Character set for file
int ch; // Current character from file
char *end; // End of buffer
id[0] = '\0';
str[0] = '\0';
ptr = NULL;
end = NULL;
while ((ch = get_utf16(fp, cs)) != 0)
{
if (ptr)
{
if (ch == '\\')
{
if ((ch = get_utf16(fp, cs)) == 0)
break;
if (ch == 'n')
ch = '\n';
else if (ch == 't')
ch = '\t';
}
else if (ch == '\"')
{
*ptr = '\0';
ptr = NULL;
}
if (ptr)
put_utf8(ch, ptr, end);
}
else if (ch == '/')
{
// Start of a comment?
if ((ch = get_utf16(fp, cs)) == 0)
break;
if (ch == '*')
{
// Skip C comment...
int lastch = 0;
while ((ch = get_utf16(fp, cs)) != 0)
{
if (ch == '/' && lastch == '*')
break;
lastch = ch;
}
}
else if (ch == '/')
{
// Skip C++ comment...
while ((ch = get_utf16(fp, cs)) != 0)
if (ch == '\n')
break;
}
}
else if (ch == '\"')
{
// Start quoted string...
if (id[0])
{
ptr = str;
end = str + sizeof(str) - 1;
}
else
{
ptr = id;
end = id + sizeof(id) - 1;
}
}
else if (ch == ';')
{
// Add string...
add_message(id, str);
id[0] = '\0';
}
}
}
else if (!strcmp(ptr, ".po") || !strcmp(ptr, ".gz"))
{
/*
* Read messages from the catalog file until EOF...
*
* The format is the GNU gettext .po format, which is fairly simple:
*
* msgid "some text"
* msgstr "localized text"
*
* The ID and localized text can span multiple lines using the form:
*
* msgid ""
* "some long text"
* msgstr ""
* "localized text spanning "
* "multiple lines"
*/
int which, // In msgid?
haveid, // Did we get a msgid string?
havestr; // Did we get a msgstr string?
linenum = 0;
id[0] = '\0';
str[0] = '\0';
haveid = 0;
havestr = 0;
which = 0;
while (cupsFileGets(fp, line, sizeof(line)))
{
linenum ++;
// Skip blank and comment lines...
if (line[0] == '#' || !line[0])
continue;
// Strip the trailing quote...
if ((ptr = (char *)strrchr(line, '\"')) == NULL)
{
_cupsLangPrintf(stderr,
_("ppdc: Expected quoted string on line %d of %s."),
linenum, f);
cupsFileClose(fp);
return (-1);
}
*ptr = '\0';
// Find start of value...
if ((ptr = strchr(line, '\"')) == NULL)
{
_cupsLangPrintf(stderr,
_("ppdc: Expected quoted string on line %d of %s."),
linenum, f);
cupsFileClose(fp);
return (-1);
}
ptr ++;
// Unquote the text...
char *sptr, *dptr; // Source/destination pointers
for (sptr = ptr, dptr = ptr; *sptr;)
{
if (*sptr == '\\')
{
sptr ++;
if (isdigit(*sptr))
{
*dptr = 0;
while (isdigit(*sptr))
{
*dptr = *dptr * 8 + *sptr - '0';
sptr ++;
}
dptr ++;
}
else
{
if (*sptr == 'n')
*dptr++ = '\n';
else if (*sptr == 'r')
*dptr++ = '\r';
else if (*sptr == 't')
*dptr++ = '\t';
else
*dptr++ = *sptr;
sptr ++;
}
}
else
*dptr++ = *sptr++;
}
*dptr = '\0';
// Create or add to a message...
if (!strncmp(line, "msgid", 5))
{
if (haveid && havestr)
add_message(id, str);
strlcpy(id, ptr, sizeof(id));
str[0] = '\0';
haveid = 1;
havestr = 0;
which = 1;
}
else if (!strncmp(line, "msgstr", 6))
{
if (!haveid)
{
_cupsLangPrintf(stderr,
_("ppdc: Need a msgid line before any "
"translation strings on line %d of %s."),
linenum, f);
cupsFileClose(fp);
return (-1);
}
strlcpy(str, ptr, sizeof(str));
havestr = 1;
which = 2;
}
else if (line[0] == '\"' && which == 2)
strlcat(str, ptr, sizeof(str));
else if (line[0] == '\"' && which == 1)
strlcat(id, ptr, sizeof(id));
else
{
_cupsLangPrintf(stderr, _("ppdc: Unexpected text on line %d of %s."),
linenum, f);
cupsFileClose(fp);
return (-1);
}
}
if (haveid && havestr)
add_message(id, str);
}
else
goto unknown_load_format;
/*
* Close the file and return...
*/
cupsFileClose(fp);
return (0);
/*
* Unknown format error...
*/
unknown_load_format:
_cupsLangPrintf(stderr,
_("ppdc: Unknown message catalog format for \"%s\"."), f);
cupsFileClose(fp);
return (-1);
}
//
// 'ppdcCatalog::save_messages()' - Save the messages to a .po file.
//
int // O - 0 on success, -1 on error
ppdcCatalog::save_messages(
const char *f) // I - File to save to
{
cups_file_t *fp; // Message file
ppdcMessage *m; // Current message
char *ptr; // Pointer into string
int utf16; // Output UTF-16 .strings file?
int ch; // Current character
// Open the file...
if ((ptr = (char *)strrchr(f, '.')) == NULL)
return (-1);
if (!strcmp(ptr, ".gz"))
fp = cupsFileOpen(f, "w9");
else
fp = cupsFileOpen(f, "w");
if (!fp)
return (-1);
// For .strings files, write a BOM for big-endian output...
utf16 = !strcmp(ptr, ".strings");
if (utf16)
put_utf16(fp, 0xfeff);
// Loop through all of the messages...
for (m = (ppdcMessage *)messages->first();
m;
m = (ppdcMessage *)messages->next())
{
if (utf16)
{
put_utf16(fp, '\"');
ptr = m->id->value;
while ((ch = get_utf8(ptr)) != 0)
switch (ch)
{
case '\n' :
put_utf16(fp, '\\');
put_utf16(fp, 'n');
break;
case '\\' :
put_utf16(fp, '\\');
put_utf16(fp, '\\');
break;
case '\"' :
put_utf16(fp, '\\');
put_utf16(fp, '\"');
break;
default :
put_utf16(fp, ch);
break;
}
put_utf16(fp, '\"');
put_utf16(fp, ' ');
put_utf16(fp, '=');
put_utf16(fp, ' ');
put_utf16(fp, '\"');
ptr = m->string->value;
while ((ch = get_utf8(ptr)) != 0)
switch (ch)
{
case '\n' :
put_utf16(fp, '\\');
put_utf16(fp, 'n');
break;
case '\\' :
put_utf16(fp, '\\');
put_utf16(fp, '\\');
break;
case '\"' :
put_utf16(fp, '\\');
put_utf16(fp, '\"');
break;
default :
put_utf16(fp, ch);
break;
}
put_utf16(fp, '\"');
put_utf16(fp, ';');
put_utf16(fp, '\n');
}
else
{
cupsFilePuts(fp, "msgid \"");
for (ptr = m->id->value; *ptr; ptr ++)
switch (*ptr)
{
case '\n' :
cupsFilePuts(fp, "\\n");
break;
case '\\' :
cupsFilePuts(fp, "\\\\");
break;
case '\"' :
cupsFilePuts(fp, "\\\"");
break;
default :
cupsFilePutChar(fp, *ptr);
break;
}
cupsFilePuts(fp, "\"\n");
cupsFilePuts(fp, "msgstr \"");
for (ptr = m->string->value; *ptr; ptr ++)
switch (*ptr)
{
case '\n' :
cupsFilePuts(fp, "\\n");
break;
case '\\' :
cupsFilePuts(fp, "\\\\");
break;
case '\"' :
cupsFilePuts(fp, "\\\"");
break;
default :
cupsFilePutChar(fp, *ptr);
break;
}
cupsFilePuts(fp, "\"\n");
cupsFilePutChar(fp, '\n');
}
}
cupsFileClose(fp);
return (0);
}
#if defined(__APPLE__) && defined(CUPS_BUNDLEDIR)
//
// 'apple_add_message()' - Add a message from a localization dictionary.
//
static void
apple_add_message(CFStringRef key, // I - Localization key
CFStringRef val, // I - Localized value
ppdcCatalog *c) // I - Message catalog
{
char id[1024], // Message id
str[1024]; // Localized message
if (CFStringGetCString(key, id, sizeof(id), kCFStringEncodingUTF8) &&
CFStringGetCString(val, str, sizeof(str), kCFStringEncodingUTF8))
c->add_message(id, str);
}
#endif /* __APPLE__ && CUPS_BUNDLEDIR */
//
// 'get_utf8()' - Get a UTF-8 character.
//
static int // O - Unicode character or 0 on EOF
get_utf8(char *&ptr) // IO - Pointer to character
{
int ch; // Current character
if ((ch = *ptr++ & 255) < 0xc0)
return (ch);
if ((ch & 0xe0) == 0xc0)
{
// Two-byte UTF-8...
if ((*ptr & 0xc0) != 0x80)
return (0);
ch = ((ch & 0x1f) << 6) | (*ptr++ & 0x3f);
}
else if ((ch & 0xf0) == 0xe0)
{
// Three-byte UTF-8...
if ((*ptr & 0xc0) != 0x80)
return (0);
ch = ((ch & 0x0f) << 6) | (*ptr++ & 0x3f);
if ((*ptr & 0xc0) != 0x80)
return (0);
ch = (ch << 6) | (*ptr++ & 0x3f);
}
else if ((ch & 0xf8) == 0xf0)
{
// Four-byte UTF-8...
if ((*ptr & 0xc0) != 0x80)
return (0);
ch = ((ch & 0x07) << 6) | (*ptr++ & 0x3f);
if ((*ptr & 0xc0) != 0x80)
return (0);
ch = (ch << 6) | (*ptr++ & 0x3f);
if ((*ptr & 0xc0) != 0x80)
return (0);
ch = (ch << 6) | (*ptr++ & 0x3f);
}
return (ch);
}
//
// 'get_utf16()' - Get a UTF-16 character...
//
static int // O - Unicode character or 0 on EOF
get_utf16(cups_file_t *fp, // I - File to read from
ppdc_cs_t &cs) // IO - Character set of file
{
int ch; // Current character
unsigned char buffer[3]; // Bytes
if (cs == PPDC_CS_AUTO)
{
// Get byte-order-mark, if present...
if (cupsFileRead(fp, (char *)buffer, 2) != 2)
return (0);
if (buffer[0] == 0xfe && buffer[1] == 0xff)
{
// Big-endian UTF-16...
cs = PPDC_CS_UTF16BE;
if (cupsFileRead(fp, (char *)buffer, 2) != 2)
return (0);
}
else if (buffer[0] == 0xff && buffer[1] == 0xfe)
{
// Little-endian UTF-16...
cs = PPDC_CS_UTF16LE;
if (cupsFileRead(fp, (char *)buffer, 2) != 2)
return (0);
}
else if (buffer[0] == 0x00 && buffer[1] != 0x00)
{
// No BOM, assume big-endian UTF-16...
cs = PPDC_CS_UTF16BE;
}
else if (buffer[0] != 0x00 && buffer[1] == 0x00)
{
// No BOM, assume little-endian UTF-16...
cs = PPDC_CS_UTF16LE;
}
else
{
// No BOM, assume UTF-8...
cs = PPDC_CS_UTF8;
cupsFileRewind(fp);
}
}
else if (cs != PPDC_CS_UTF8)
{
if (cupsFileRead(fp, (char *)buffer, 2) != 2)
return (0);
}
if (cs == PPDC_CS_UTF8)
{
// UTF-8 character...
if ((ch = cupsFileGetChar(fp)) < 0)
return (0);
if ((ch & 0xe0) == 0xc0)
{
// Two-byte UTF-8...
if (cupsFileRead(fp, (char *)buffer, 1) != 1)
return (0);
if ((buffer[0] & 0xc0) != 0x80)
return (0);
ch = ((ch & 0x1f) << 6) | (buffer[0] & 0x3f);
}
else if ((ch & 0xf0) == 0xe0)
{
// Three-byte UTF-8...
if (cupsFileRead(fp, (char *)buffer, 2) != 2)
return (0);
if ((buffer[0] & 0xc0) != 0x80 ||
(buffer[1] & 0xc0) != 0x80)
return (0);
ch = ((((ch & 0x0f) << 6) | (buffer[0] & 0x3f)) << 6) |
(buffer[1] & 0x3f);
}
else if ((ch & 0xf8) == 0xf0)
{
// Four-byte UTF-8...
if (cupsFileRead(fp, (char *)buffer, 3) != 3)
return (0);
if ((buffer[0] & 0xc0) != 0x80 ||
(buffer[1] & 0xc0) != 0x80 ||
(buffer[2] & 0xc0) != 0x80)
return (0);
ch = ((((((ch & 0x07) << 6) | (buffer[0] & 0x3f)) << 6) |
(buffer[1] & 0x3f)) << 6) | (buffer[2] & 0x3f);
}
}
else
{
// UTF-16 character...
if (cs == PPDC_CS_UTF16BE)
ch = (buffer[0] << 8) | buffer[1];
else
ch = (buffer[1] << 8) | buffer[0];
if (ch >= 0xd800 && ch <= 0xdbff)
{
// Handle multi-word encoding...
int lch;
if (cupsFileRead(fp, (char *)buffer, 2) != 2)
return (0);
if (cs == PPDC_CS_UTF16BE)
lch = (buffer[0] << 8) | buffer[1];
else
lch = (buffer[1] << 8) | buffer[0];
if (lch < 0xdc00 || lch >= 0xdfff)
return (0);
ch = (((ch & 0x3ff) << 10) | (lch & 0x3ff)) + 0x10000;
}
}
return (ch);
}
//
// 'put_utf8()' - Add a UTF-8 character to a string.
//
static int // O - 0 on success, -1 on failure
put_utf8(int ch, // I - Unicode character
char *&ptr, // IO - String pointer
char *end) // I - End of buffer
{
if (ch < 0x80)
{
// One-byte ASCII...
if (ptr >= end)
return (-1);
*ptr++ = (char)ch;
}
else if (ch < 0x800)
{
// Two-byte UTF-8...
if ((ptr + 1) >= end)
return (-1);
*ptr++ = (char)(0xc0 | (ch >> 6));
*ptr++ = (char)(0x80 | (ch & 0x3f));
}
else if (ch < 0x10000)
{
// Three-byte UTF-8...
if ((ptr + 2) >= end)
return (-1);
*ptr++ = (char)(0xe0 | (ch >> 12));
*ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
*ptr++ = (char)(0x80 | (ch & 0x3f));
}
else
{
// Four-byte UTF-8...
if ((ptr + 3) >= end)
return (-1);
*ptr++ = (char)(0xf0 | (ch >> 18));
*ptr++ = (char)(0x80 | ((ch >> 12) & 0x3f));
*ptr++ = (char)(0x80 | ((ch >> 6) & 0x3f));
*ptr++ = (char)(0x80 | (ch & 0x3f));
}
return (0);
}
//
// 'put_utf16()' - Write a UTF-16 character to a file.
//
static int // O - 0 on success, -1 on failure
put_utf16(cups_file_t *fp, // I - File to write to
int ch) // I - Unicode character
{
unsigned char buffer[4]; // Output buffer
if (ch < 0x10000)
{
// One-word UTF-16 big-endian...
buffer[0] = (unsigned char)(ch >> 8);
buffer[1] = (unsigned char)ch;
if (cupsFileWrite(fp, (char *)buffer, 2) == 2)
return (0);
}
else
{
// Two-word UTF-16 big-endian...
ch -= 0x10000;
buffer[0] = (unsigned char)(0xd8 | (ch >> 18));
buffer[1] = (unsigned char)(ch >> 10);
buffer[2] = (unsigned char)(0xdc | ((ch >> 8) & 0x03));
buffer[3] = (unsigned char)ch;
if (cupsFileWrite(fp, (char *)buffer, 4) == 4)
return (0);
}
return (-1);
}