/*
- * "$Id: po2strings.c 6921 2007-09-06 13:38:37Z mike $"
+ * "$Id$"
*
- * Convert GNU gettext .po files to Apple .strings file (UTF-16 BE text file).
+ * Convert a GNU gettext .po file to an Apple .strings file.
+ *
+ * Copyright 2007-2015 by Apple Inc.
+ *
+ * These coded instructions, statements, and computer programs are the
+ * property of Apple Inc. and are protected by Federal copyright
+ * law. Distribution and use rights are outlined in the file "LICENSE.txt"
+ * which should have been included with this file. If this file is
+ * file is missing or damaged, see the license at "http://www.cups.org/".
*
* Usage:
*
* Compile with:
*
* gcc -o po2strings po2strings.c `cups-config --libs`
- *
- * Contents:
- *
- * main() - Convert .po file to .strings.
- * write_string() - Write a string to the .strings file.
*/
-#include <stdio.h>
-#include <stdlib.h>
-#include <cups/i18n.h>
+#include <cups/cups-private.h>
/*
* The .strings file format is simple:
*
* // comment
- * "id" = "str";
+ * "msgid" = "msgstr";
*
- * Both the id and str strings use standard C quoting for special characters
- * like newline and the double quote character.
- */
-
-/*
- * Local functions...
+ * 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 void write_string(FILE *strings, const char *s);
+static char *normalize_string(const char *idstr, char *buffer, size_t bufsize);
/*
main(int argc, /* I - Number of command-line args */
char *argv[]) /* I - Command-line arguments */
{
- FILE *strings; /* .strings file */
- cups_array_t *po; /* .po file */
- char iconv[1024]; /* iconv command */
- _cups_message_t *msg; /* Current message */
+ 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 (argc != 3)
+ if (!pofile || !stringsfile)
{
- puts("Usage: po2strings filename.po filename.strings");
+ puts("Usage: po2strings [-m] filename.po filename.strings");
return (1);
}
/*
- * Use the CUPS .po loader to get the message strings...
+ * Read strings from the .po file and write to the .strings file...
*/
- if ((po = _cupsMessageLoad(argv[1])) == NULL)
+ if ((po = cupsFileOpen(pofile, "r")) == NULL)
{
- perror(argv[1]);
+ perror(pofile);
return (1);
}
- /*
- * Cheat by using iconv to write the .strings file with a UTF-16 encoding.
- * The .po file uses UTF-8...
- */
-
- snprintf(iconv, sizeof(iconv), "iconv -f utf-8 -t utf-16 >'%s'", argv[2]);
- if ((strings = popen(iconv, "w")) == NULL)
+ if ((strings = cupsFileOpen(stringsfile, "w")) == NULL)
{
- perror(argv[2]);
- _cupsMessageFree(po);
+ perror(stringsfile);
+ cupsFileClose(po);
return (1);
}
- for (msg = (_cups_message_t *)cupsArrayFirst(po);
- msg;
- msg = (_cups_message_t *)cupsArrayNext(po))
+ 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)
{
- write_string(strings, msg->id);
- fputs(" = ", strings);
- write_string(strings, msg->str);
- fputs(";\n", strings);
+ if (*msgid)
+ cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));
}
- printf("%s: %d messages.\n", argv[2], cupsArrayCount(po));
+ if (msgid)
+ free(msgid);
- pclose(strings);
- _cupsMessageFree(po);
+ if (msgstr)
+ free(msgstr);
+
+ cupsFileClose(po);
+ cupsFileClose(strings);
return (0);
}
/*
- * 'write_string()' - Write a string to the .strings file.
+ * 'normalize_string()' - Normalize a msgid string.
+ *
+ * This function converts ASCII ellipsis and double quotes to their Unicode
+ * counterparts.
*/
-static void
-write_string(FILE *strings, /* I - .strings file */
- const char *s) /* I - String to write */
+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 */
{
- putc('\"', strings);
+ char *bufptr = buffer, /* Pointer into buffer */
+ *bufend = buffer + bufsize - 3; /* End of buffer */
+ int quote = 0, /* Quote direction */
+ html = 0; /* HTML text */
- while (*s)
+
+ while (*idstr && bufptr < bufend)
{
- switch (*s)
+ if (!strncmp(idstr, "<A ", 3))
+ html = 1;
+ else if (html && *idstr == '>')
+ html = 0;
+
+ if (*idstr == '.' && idstr[1] == '.' && idstr[2] == '.')
{
- case '\n' :
- fputs("\\n", strings);
- break;
- case '\t' :
- fputs("\\t", strings);
- break;
- case '\\' :
- fputs("\\\\", strings);
- break;
- case '\"' :
- fputs("\\\"", strings);
- break;
- default :
- putc(*s, strings);
- break;
+ /*
+ * Convert ... to Unicode ellipsis...
+ */
+
+ *bufptr++ = (char)0xE2;
+ *bufptr++ = (char)0x80;
+ *bufptr++ = (char)0xA6;
+ idstr += 2;
}
+ else if (!html && *idstr == '\\' && idstr[1] == '\"' && (quote || strchr(idstr + 2, '\"') != NULL))
+ {
+ if (quote)
+ {
+ /*
+ * Convert \" to Unicode right (curley) double quote.
+ */
+
+ *bufptr++ = (char)0xE2;
+ *bufptr++ = (char)0x80;
+ *bufptr++ = (char)0x9D;
+ }
+ else
+ {
+ /*
+ * Convert \" to Unicode left (curley) double quote.
+ */
- s ++;
+ *bufptr++ = (char)0xE2;
+ *bufptr++ = (char)0x80;
+ *bufptr++ = (char)0x9C;
+ }
+
+ quote = !quote;
+ idstr ++;
+ }
+ else
+ *bufptr++ = *idstr;
+
+ idstr ++;
}
- putc('\"', strings);
+ *bufptr = '\0';
+
+ return (buffer);
}
/*
- * End of "$Id: po2strings.c 6921 2007-09-06 13:38:37Z mike $".
+ * End of "$Id$".
*/