/*1* Convert a GNU gettext .po file to an Apple .strings file.2*3* Copyright 2007-2017 by Apple Inc.4*5* Licensed under Apache License v2.0. See the file "LICENSE" for more information.6*7* Usage:8*9* po2strings filename.strings filename.po10*11* Compile with:12*13* gcc -o po2strings po2strings.c `cups-config --libs`14*/1516#include <cups/cups-private.h>171819/*20* The .strings file format is simple:21*22* // comment23* "msgid" = "msgstr";24*25* The GNU gettext .po format is also fairly simple:26*27* #. comment28* msgid "some text"29* msgstr "localized text"30*31* The comment, msgid, and msgstr text can span multiple lines using the form:32*33* #. comment34* #. more comments35* msgid ""36* "some long text"37* msgstr ""38* "localized text spanning "39* "multiple lines"40*41* Both the msgid and msgstr strings use standard C quoting for special42* characters like newline and the double quote character.43*/4445static char *normalize_string(const char *idstr, char *buffer, size_t bufsize);464748/*49* main() - Convert .po file to .strings.50*/5152int /* O - Exit code */53main(int argc, /* I - Number of command-line args */54char *argv[]) /* I - Command-line arguments */55{56int i; /* Looping var */57const char *pofile, /* .po filename */58*stringsfile; /* .strings filename */59cups_file_t *po, /* .po file */60*strings; /* .strings file */61char s[4096], /* String buffer */62*ptr, /* Pointer into buffer */63*temp, /* New string */64*msgid, /* msgid string */65*msgstr, /* msgstr string */66normalized[8192];/* Normalized msgid string */67size_t length; /* Length of combined strings */68int use_msgid; /* Use msgid strings for msgstr? */697071/*72* Process command-line arguments...73*/7475pofile = NULL;76stringsfile = NULL;77use_msgid = 0;7879for (i = 1; i < argc; i ++)80{81if (!strcmp(argv[i], "-m"))82use_msgid = 1;83else if (argv[i][0] == '-')84{85puts("Usage: po2strings [-m] filename.po filename.strings");86return (1);87}88else if (!pofile)89pofile = argv[i];90else if (!stringsfile)91stringsfile = argv[i];92else93{94puts("Usage: po2strings [-m] filename.po filename.strings");95return (1);96}97}9899if (!pofile || !stringsfile)100{101puts("Usage: po2strings [-m] filename.po filename.strings");102return (1);103}104105/*106* Read strings from the .po file and write to the .strings file...107*/108109if ((po = cupsFileOpen(pofile, "r")) == NULL)110{111perror(pofile);112return (1);113}114115if ((strings = cupsFileOpen(stringsfile, "w")) == NULL)116{117perror(stringsfile);118cupsFileClose(po);119return (1);120}121122msgid = msgstr = NULL;123124while (cupsFileGets(po, s, sizeof(s)) != NULL)125{126if (s[0] == '#' && s[1] == '.')127{128/*129* Copy comment string...130*/131132if (msgid && msgstr)133{134/*135* First output the last localization string...136*/137138if (*msgid)139cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid,140(use_msgid || !*msgstr) ? msgid : msgstr);141142free(msgid);143free(msgstr);144msgid = msgstr = NULL;145}146147cupsFilePrintf(strings, "//%s\n", s + 2);148}149else if (s[0] == '#' || !s[0])150{151/*152* Skip blank and file comment lines...153*/154155continue;156}157else158{159/*160* Strip the trailing quote...161*/162163if ((ptr = strrchr(s, '\"')) == NULL)164continue;165166*ptr = '\0';167168/*169* Find start of value...170*/171172if ((ptr = strchr(s, '\"')) == NULL)173continue;174175ptr ++;176177/*178* Create or add to a message...179*/180181if (!strncmp(s, "msgid", 5))182{183/*184* Output previous message as needed...185*/186187if (msgid && msgstr)188{189if (*msgid)190cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));191}192193if (msgid)194free(msgid);195196if (msgstr)197free(msgstr);198199msgid = strdup(ptr);200msgstr = NULL;201}202else if (s[0] == '\"' && (msgid || msgstr))203{204/*205* Append to current string...206*/207208size_t ptrlen = strlen(ptr); /* Length of string */209210length = strlen(msgstr ? msgstr : msgid);211212if ((temp = realloc(msgstr ? msgstr : msgid,213length + ptrlen + 1)) == NULL)214{215free(msgid);216if (msgstr)217free(msgstr);218perror("Unable to allocate string");219return (1);220}221222if (msgstr)223{224/*225* Copy the new portion to the end of the msgstr string - safe226* to use strcpy because the buffer is allocated to the correct227* size...228*/229230msgstr = temp;231232memcpy(msgstr + length, ptr, ptrlen + 1);233}234else235{236/*237* Copy the new portion to the end of the msgid string - safe238* to use strcpy because the buffer is allocated to the correct239* size...240*/241242msgid = temp;243244memcpy(msgid + length, ptr, ptrlen + 1);245}246}247else if (!strncmp(s, "msgstr", 6) && msgid)248{249/*250* Set the string...251*/252253if (msgstr)254free(msgstr);255256if ((msgstr = strdup(ptr)) == NULL)257{258free(msgid);259perror("Unable to allocate msgstr");260return (1);261}262}263}264}265266if (msgid && msgstr)267{268if (*msgid)269cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));270}271272if (msgid)273free(msgid);274275if (msgstr)276free(msgstr);277278cupsFileClose(po);279cupsFileClose(strings);280281return (0);282}283284285/*286* 'normalize_string()' - Normalize a msgid string.287*288* This function converts ASCII ellipsis and double quotes to their Unicode289* counterparts.290*/291292static char * /* O - Normalized string */293normalize_string(const char *idstr, /* I - msgid string */294char *buffer, /* I - Normalized string buffer */295size_t bufsize) /* I - Size of string buffer */296{297char *bufptr = buffer, /* Pointer into buffer */298*bufend = buffer + bufsize - 3; /* End of buffer */299int quote = 0, /* Quote direction */300html = 0; /* HTML text */301302303while (*idstr && bufptr < bufend)304{305if (!strncmp(idstr, "<A ", 3))306html = 1;307else if (html && *idstr == '>')308html = 0;309310if (*idstr == '.' && idstr[1] == '.' && idstr[2] == '.')311{312/*313* Convert ... to Unicode ellipsis...314*/315316*bufptr++ = (char)0xE2;317*bufptr++ = (char)0x80;318*bufptr++ = (char)0xA6;319idstr += 2;320}321else if (!html && *idstr == '\\' && idstr[1] == '\"')322{323if (quote)324{325/*326* Convert second \" to Unicode right (curley) double quote.327*/328329*bufptr++ = (char)0xE2;330*bufptr++ = (char)0x80;331*bufptr++ = (char)0x9D;332quote = 0;333}334else if (strchr(idstr + 2, '\"') != NULL)335{336/*337* Convert first \" to Unicode left (curley) double quote.338*/339340*bufptr++ = (char)0xE2;341*bufptr++ = (char)0x80;342*bufptr++ = (char)0x9C;343quote = 1;344}345else346{347/*348* Convert lone \" to Unicode double prime.349*/350351*bufptr++ = (char)0xE2;352*bufptr++ = (char)0x80;353*bufptr++ = (char)0xB3;354}355356idstr ++;357}358else if (*idstr == '\'')359{360if (strchr(idstr + 1, '\'') == NULL || quote)361{362/*363* Convert second ' (or ' used for a contraction) to Unicode right364* (curley) single quote.365*/366367*bufptr++ = (char)0xE2;368*bufptr++ = (char)0x80;369*bufptr++ = (char)0x99;370quote = 0;371}372else373{374/*375* Convert first ' to Unicode left (curley) single quote.376*/377378*bufptr++ = (char)0xE2;379*bufptr++ = (char)0x80;380*bufptr++ = (char)0x98;381quote = 1;382}383}384else385*bufptr++ = *idstr;386387idstr ++;388}389390*bufptr = '\0';391392return (buffer);393}394395396