| /* |
| * Convert a GNU gettext .po file to an Apple .strings file. |
| * |
| * Copyright 2007-2017 by Apple Inc. |
| * |
| * Licensed under Apache License v2.0. See the file "LICENSE" for more information. |
| * |
| * Usage: |
| * |
| * po2strings filename.strings filename.po |
| * |
| * Compile with: |
| * |
| * gcc -o po2strings po2strings.c `cups-config --libs` |
| */ |
| |
| #include <cups/cups-private.h> |
| |
| |
| /* |
| * The .strings file format is simple: |
| * |
| * // comment |
| * "msgid" = "msgstr"; |
| * |
| * The GNU gettext .po format is also fairly simple: |
| * |
| * #. comment |
| * msgid "some text" |
| * msgstr "localized text" |
| * |
| * The comment, msgid, and msgstr text can span multiple lines using the form: |
| * |
| * #. comment |
| * #. more comments |
| * msgid "" |
| * "some long text" |
| * msgstr "" |
| * "localized text spanning " |
| * "multiple lines" |
| * |
| * Both the msgid and msgstr strings use standard C quoting for special |
| * characters like newline and the double quote character. |
| */ |
| |
| static char *normalize_string(const char *idstr, char *buffer, size_t bufsize); |
| |
| |
| /* |
| * main() - Convert .po file to .strings. |
| */ |
| |
| int /* O - Exit code */ |
| main(int argc, /* I - Number of command-line args */ |
| char *argv[]) /* I - Command-line arguments */ |
| { |
| int i; /* Looping var */ |
| const char *pofile, /* .po filename */ |
| *stringsfile; /* .strings filename */ |
| cups_file_t *po, /* .po file */ |
| *strings; /* .strings file */ |
| char s[4096], /* String buffer */ |
| *ptr, /* Pointer into buffer */ |
| *temp, /* New string */ |
| *msgid, /* msgid string */ |
| *msgstr, /* msgstr string */ |
| normalized[8192];/* Normalized msgid string */ |
| size_t length; /* Length of combined strings */ |
| int use_msgid; /* Use msgid strings for msgstr? */ |
| |
| |
| /* |
| * Process command-line arguments... |
| */ |
| |
| pofile = NULL; |
| stringsfile = NULL; |
| use_msgid = 0; |
| |
| for (i = 1; i < argc; i ++) |
| { |
| if (!strcmp(argv[i], "-m")) |
| use_msgid = 1; |
| else if (argv[i][0] == '-') |
| { |
| puts("Usage: po2strings [-m] filename.po filename.strings"); |
| return (1); |
| } |
| else if (!pofile) |
| pofile = argv[i]; |
| else if (!stringsfile) |
| stringsfile = argv[i]; |
| else |
| { |
| puts("Usage: po2strings [-m] filename.po filename.strings"); |
| return (1); |
| } |
| } |
| |
| if (!pofile || !stringsfile) |
| { |
| puts("Usage: po2strings [-m] filename.po filename.strings"); |
| return (1); |
| } |
| |
| /* |
| * Read strings from the .po file and write to the .strings file... |
| */ |
| |
| if ((po = cupsFileOpen(pofile, "r")) == NULL) |
| { |
| perror(pofile); |
| return (1); |
| } |
| |
| if ((strings = cupsFileOpen(stringsfile, "w")) == NULL) |
| { |
| perror(stringsfile); |
| cupsFileClose(po); |
| return (1); |
| } |
| |
| msgid = msgstr = NULL; |
| |
| while (cupsFileGets(po, s, sizeof(s)) != NULL) |
| { |
| if (s[0] == '#' && s[1] == '.') |
| { |
| /* |
| * Copy comment string... |
| */ |
| |
| if (msgid && msgstr) |
| { |
| /* |
| * First output the last localization string... |
| */ |
| |
| if (*msgid) |
| cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, |
| (use_msgid || !*msgstr) ? msgid : msgstr); |
| |
| free(msgid); |
| free(msgstr); |
| msgid = msgstr = NULL; |
| } |
| |
| cupsFilePrintf(strings, "//%s\n", s + 2); |
| } |
| else if (s[0] == '#' || !s[0]) |
| { |
| /* |
| * Skip blank and file comment lines... |
| */ |
| |
| continue; |
| } |
| else |
| { |
| /* |
| * Strip the trailing quote... |
| */ |
| |
| if ((ptr = strrchr(s, '\"')) == NULL) |
| continue; |
| |
| *ptr = '\0'; |
| |
| /* |
| * Find start of value... |
| */ |
| |
| if ((ptr = strchr(s, '\"')) == NULL) |
| continue; |
| |
| ptr ++; |
| |
| /* |
| * Create or add to a message... |
| */ |
| |
| if (!strncmp(s, "msgid", 5)) |
| { |
| /* |
| * Output previous message as needed... |
| */ |
| |
| if (msgid && msgstr) |
| { |
| if (*msgid) |
| cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized))); |
| } |
| |
| if (msgid) |
| free(msgid); |
| |
| if (msgstr) |
| free(msgstr); |
| |
| msgid = strdup(ptr); |
| msgstr = NULL; |
| } |
| else if (s[0] == '\"' && (msgid || msgstr)) |
| { |
| /* |
| * Append to current string... |
| */ |
| |
| size_t ptrlen = strlen(ptr); /* Length of string */ |
| |
| length = strlen(msgstr ? msgstr : msgid); |
| |
| if ((temp = realloc(msgstr ? msgstr : msgid, |
| length + ptrlen + 1)) == NULL) |
| { |
| free(msgid); |
| if (msgstr) |
| free(msgstr); |
| perror("Unable to allocate string"); |
| return (1); |
| } |
| |
| if (msgstr) |
| { |
| /* |
| * Copy the new portion to the end of the msgstr string - safe |
| * to use strcpy because the buffer is allocated to the correct |
| * size... |
| */ |
| |
| msgstr = temp; |
| |
| memcpy(msgstr + length, ptr, ptrlen + 1); |
| } |
| else |
| { |
| /* |
| * Copy the new portion to the end of the msgid string - safe |
| * to use strcpy because the buffer is allocated to the correct |
| * size... |
| */ |
| |
| msgid = temp; |
| |
| memcpy(msgid + length, ptr, ptrlen + 1); |
| } |
| } |
| else if (!strncmp(s, "msgstr", 6) && msgid) |
| { |
| /* |
| * Set the string... |
| */ |
| |
| if (msgstr) |
| free(msgstr); |
| |
| if ((msgstr = strdup(ptr)) == NULL) |
| { |
| free(msgid); |
| perror("Unable to allocate msgstr"); |
| return (1); |
| } |
| } |
| } |
| } |
| |
| if (msgid && msgstr) |
| { |
| if (*msgid) |
| cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized))); |
| } |
| |
| if (msgid) |
| free(msgid); |
| |
| if (msgstr) |
| free(msgstr); |
| |
| cupsFileClose(po); |
| cupsFileClose(strings); |
| |
| return (0); |
| } |
| |
| |
| /* |
| * 'normalize_string()' - Normalize a msgid string. |
| * |
| * This function converts ASCII ellipsis and double quotes to their Unicode |
| * counterparts. |
| */ |
| |
| static char * /* O - Normalized string */ |
| normalize_string(const char *idstr, /* I - msgid string */ |
| char *buffer, /* I - Normalized string buffer */ |
| size_t bufsize) /* I - Size of string buffer */ |
| { |
| char *bufptr = buffer, /* Pointer into buffer */ |
| *bufend = buffer + bufsize - 3; /* End of buffer */ |
| int quote = 0, /* Quote direction */ |
| html = 0; /* HTML text */ |
| |
| |
| while (*idstr && bufptr < bufend) |
| { |
| if (!strncmp(idstr, "<A ", 3)) |
| html = 1; |
| else if (html && *idstr == '>') |
| html = 0; |
| |
| if (*idstr == '.' && idstr[1] == '.' && idstr[2] == '.') |
| { |
| /* |
| * Convert ... to Unicode ellipsis... |
| */ |
| |
| *bufptr++ = (char)0xE2; |
| *bufptr++ = (char)0x80; |
| *bufptr++ = (char)0xA6; |
| idstr += 2; |
| } |
| else if (!html && *idstr == '\\' && idstr[1] == '\"') |
| { |
| if (quote) |
| { |
| /* |
| * Convert second \" to Unicode right (curley) double quote. |
| */ |
| |
| *bufptr++ = (char)0xE2; |
| *bufptr++ = (char)0x80; |
| *bufptr++ = (char)0x9D; |
| quote = 0; |
| } |
| else if (strchr(idstr + 2, '\"') != NULL) |
| { |
| /* |
| * Convert first \" to Unicode left (curley) double quote. |
| */ |
| |
| *bufptr++ = (char)0xE2; |
| *bufptr++ = (char)0x80; |
| *bufptr++ = (char)0x9C; |
| quote = 1; |
| } |
| else |
| { |
| /* |
| * Convert lone \" to Unicode double prime. |
| */ |
| |
| *bufptr++ = (char)0xE2; |
| *bufptr++ = (char)0x80; |
| *bufptr++ = (char)0xB3; |
| } |
| |
| idstr ++; |
| } |
| else if (*idstr == '\'') |
| { |
| if (strchr(idstr + 1, '\'') == NULL || quote) |
| { |
| /* |
| * Convert second ' (or ' used for a contraction) to Unicode right |
| * (curley) single quote. |
| */ |
| |
| *bufptr++ = (char)0xE2; |
| *bufptr++ = (char)0x80; |
| *bufptr++ = (char)0x99; |
| quote = 0; |
| } |
| else |
| { |
| /* |
| * Convert first ' to Unicode left (curley) single quote. |
| */ |
| |
| *bufptr++ = (char)0xE2; |
| *bufptr++ = (char)0x80; |
| *bufptr++ = (char)0x98; |
| quote = 1; |
| } |
| } |
| else |
| *bufptr++ = *idstr; |
| |
| idstr ++; |
| } |
| |
| *bufptr = '\0'; |
| |
| return (buffer); |
| } |