• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Convert a GNU gettext .po file to an Apple .strings file.
3  *
4  * Copyright 2007-2017 by Apple Inc.
5  *
6  * Licensed under Apache License v2.0.  See the file "LICENSE" for more information.
7  *
8  * Usage:
9  *
10  *   po2strings filename.strings filename.po
11  *
12  * Compile with:
13  *
14  *   gcc -o po2strings po2strings.c `cups-config --libs`
15  */
16 
17 #include <cups/cups-private.h>
18 
19 
20 /*
21  * The .strings file format is simple:
22  *
23  * // comment
24  * "msgid" = "msgstr";
25  *
26  * The GNU gettext .po format is also fairly simple:
27  *
28  *     #. comment
29  *     msgid "some text"
30  *     msgstr "localized text"
31  *
32  * The comment, msgid, and msgstr text can span multiple lines using the form:
33  *
34  *     #. comment
35  *     #. more comments
36  *     msgid ""
37  *     "some long text"
38  *     msgstr ""
39  *     "localized text spanning "
40  *     "multiple lines"
41  *
42  * Both the msgid and msgstr strings use standard C quoting for special
43  * characters like newline and the double quote character.
44  */
45 
46 static char	*normalize_string(const char *idstr, char *buffer, size_t bufsize);
47 
48 
49 /*
50  *   main() - Convert .po file to .strings.
51  */
52 
53 int					/* O - Exit code */
main(int argc,char * argv[])54 main(int  argc,				/* I - Number of command-line args */
55      char *argv[])			/* I - Command-line arguments */
56 {
57   int			i;		/* Looping var */
58   const char		*pofile,	/* .po filename */
59 			*stringsfile;	/* .strings filename */
60   cups_file_t		*po,		/* .po file */
61 			*strings;	/* .strings file */
62   char			s[4096],	/* String buffer */
63 			*ptr,		/* Pointer into buffer */
64 			*temp,		/* New string */
65 			*msgid,		/* msgid string */
66 			*msgstr,	/* msgstr string */
67 			normalized[8192];/* Normalized msgid string */
68   size_t		length;		/* Length of combined strings */
69   int			use_msgid;	/* Use msgid strings for msgstr? */
70 
71 
72  /*
73   * Process command-line arguments...
74   */
75 
76   pofile      = NULL;
77   stringsfile = NULL;
78   use_msgid   = 0;
79 
80   for (i = 1; i < argc; i ++)
81   {
82     if (!strcmp(argv[i], "-m"))
83       use_msgid = 1;
84     else if (argv[i][0] == '-')
85     {
86       puts("Usage: po2strings [-m] filename.po filename.strings");
87       return (1);
88     }
89     else if (!pofile)
90       pofile = argv[i];
91     else if (!stringsfile)
92       stringsfile = argv[i];
93     else
94     {
95       puts("Usage: po2strings [-m] filename.po filename.strings");
96       return (1);
97     }
98   }
99 
100   if (!pofile || !stringsfile)
101   {
102     puts("Usage: po2strings [-m] filename.po filename.strings");
103     return (1);
104   }
105 
106  /*
107   * Read strings from the .po file and write to the .strings file...
108   */
109 
110   if ((po = cupsFileOpen(pofile, "r")) == NULL)
111   {
112     perror(pofile);
113     return (1);
114   }
115 
116   if ((strings = cupsFileOpen(stringsfile, "w")) == NULL)
117   {
118     perror(stringsfile);
119     cupsFileClose(po);
120     return (1);
121   }
122 
123   msgid = msgstr = NULL;
124 
125   while (cupsFileGets(po, s, sizeof(s)) != NULL)
126   {
127     if (s[0] == '#' && s[1] == '.')
128     {
129      /*
130       * Copy comment string...
131       */
132 
133       if (msgid && msgstr)
134       {
135        /*
136         * First output the last localization string...
137 	*/
138 
139 	if (*msgid)
140 	  cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid,
141 			 (use_msgid || !*msgstr) ? msgid : msgstr);
142 
143 	free(msgid);
144 	free(msgstr);
145 	msgid = msgstr = NULL;
146       }
147 
148       cupsFilePrintf(strings, "//%s\n", s + 2);
149     }
150     else if (s[0] == '#' || !s[0])
151     {
152      /*
153       * Skip blank and file comment lines...
154       */
155 
156       continue;
157     }
158     else
159     {
160      /*
161       * Strip the trailing quote...
162       */
163 
164       if ((ptr = strrchr(s, '\"')) == NULL)
165 	continue;
166 
167       *ptr = '\0';
168 
169      /*
170       * Find start of value...
171       */
172 
173       if ((ptr = strchr(s, '\"')) == NULL)
174 	continue;
175 
176       ptr ++;
177 
178      /*
179       * Create or add to a message...
180       */
181 
182       if (!strncmp(s, "msgid", 5))
183       {
184        /*
185 	* Output previous message as needed...
186 	*/
187 
188         if (msgid && msgstr)
189 	{
190 	  if (*msgid)
191             cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));
192 	}
193 
194 	if (msgid)
195 	  free(msgid);
196 
197 	if (msgstr)
198 	  free(msgstr);
199 
200         msgid  = strdup(ptr);
201 	msgstr = NULL;
202       }
203       else if (s[0] == '\"' && (msgid || msgstr))
204       {
205        /*
206 	* Append to current string...
207 	*/
208 
209         size_t ptrlen = strlen(ptr);	/* Length of string */
210 
211 	length = strlen(msgstr ? msgstr : msgid);
212 
213 	if ((temp = realloc(msgstr ? msgstr : msgid,
214 			    length + ptrlen + 1)) == NULL)
215 	{
216 	  free(msgid);
217 	  if (msgstr)
218 	    free(msgstr);
219 	  perror("Unable to allocate string");
220 	  return (1);
221 	}
222 
223 	if (msgstr)
224 	{
225 	 /*
226 	  * Copy the new portion to the end of the msgstr string - safe
227 	  * to use strcpy because the buffer is allocated to the correct
228 	  * size...
229 	  */
230 
231 	  msgstr = temp;
232 
233 	  memcpy(msgstr + length, ptr, ptrlen + 1);
234 	}
235 	else
236 	{
237 	 /*
238 	  * Copy the new portion to the end of the msgid string - safe
239 	  * to use strcpy because the buffer is allocated to the correct
240 	  * size...
241 	  */
242 
243 	  msgid = temp;
244 
245 	  memcpy(msgid + length, ptr, ptrlen + 1);
246 	}
247       }
248       else if (!strncmp(s, "msgstr", 6) && msgid)
249       {
250        /*
251 	* Set the string...
252 	*/
253 
254         if (msgstr)
255           free(msgstr);
256 
257 	if ((msgstr = strdup(ptr)) == NULL)
258 	{
259 	  free(msgid);
260 	  perror("Unable to allocate msgstr");
261 	  return (1);
262 	}
263       }
264     }
265   }
266 
267   if (msgid && msgstr)
268   {
269     if (*msgid)
270       cupsFilePrintf(strings, "\"%s\" = \"%s\";\n", msgid, normalize_string((use_msgid || !*msgstr) ? msgid : msgstr, normalized, sizeof(normalized)));
271   }
272 
273   if (msgid)
274     free(msgid);
275 
276   if (msgstr)
277     free(msgstr);
278 
279   cupsFileClose(po);
280   cupsFileClose(strings);
281 
282   return (0);
283 }
284 
285 
286 /*
287  * 'normalize_string()' - Normalize a msgid string.
288  *
289  * This function converts ASCII ellipsis and double quotes to their Unicode
290  * counterparts.
291  */
292 
293 static char *				/* O - Normalized string */
normalize_string(const char * idstr,char * buffer,size_t bufsize)294 normalize_string(const char *idstr,	/* I - msgid string */
295                  char       *buffer,	/* I - Normalized string buffer */
296                  size_t     bufsize)	/* I - Size of string buffer */
297 {
298   char	*bufptr = buffer,		/* Pointer into buffer */
299 	*bufend = buffer + bufsize - 3;	/* End of buffer */
300   int	quote = 0,			/* Quote direction */
301 	html = 0;			/* HTML text */
302 
303 
304   while (*idstr && bufptr < bufend)
305   {
306     if (!strncmp(idstr, "<A ", 3))
307       html = 1;
308     else if (html && *idstr == '>')
309       html = 0;
310 
311     if (*idstr == '.' && idstr[1] == '.' && idstr[2] == '.')
312     {
313      /*
314       * Convert ... to Unicode ellipsis...
315       */
316 
317       *bufptr++ = (char)0xE2;
318       *bufptr++ = (char)0x80;
319       *bufptr++ = (char)0xA6;
320       idstr += 2;
321     }
322     else if (!html && *idstr == '\\' && idstr[1] == '\"')
323     {
324       if (quote)
325       {
326        /*
327         * Convert second \" to Unicode right (curley) double quote.
328         */
329 
330 	*bufptr++ = (char)0xE2;
331 	*bufptr++ = (char)0x80;
332 	*bufptr++ = (char)0x9D;
333 	quote     = 0;
334       }
335       else if (strchr(idstr + 2, '\"') != NULL)
336       {
337        /*
338         * Convert first \" to Unicode left (curley) double quote.
339         */
340 
341 	*bufptr++ = (char)0xE2;
342 	*bufptr++ = (char)0x80;
343 	*bufptr++ = (char)0x9C;
344 	quote     = 1;
345       }
346       else
347       {
348        /*
349         * Convert lone \" to Unicode double prime.
350         */
351 
352         *bufptr++ = (char)0xE2;
353         *bufptr++ = (char)0x80;
354         *bufptr++ = (char)0xB3;
355       }
356 
357       idstr ++;
358     }
359     else if (*idstr == '\'')
360     {
361       if (strchr(idstr + 1, '\'') == NULL || quote)
362       {
363        /*
364         * Convert second ' (or ' used for a contraction) to Unicode right
365         * (curley) single quote.
366         */
367 
368 	*bufptr++ = (char)0xE2;
369 	*bufptr++ = (char)0x80;
370 	*bufptr++ = (char)0x99;
371 	quote     = 0;
372       }
373       else
374       {
375        /*
376         * Convert first ' to Unicode left (curley) single quote.
377         */
378 
379 	*bufptr++ = (char)0xE2;
380 	*bufptr++ = (char)0x80;
381 	*bufptr++ = (char)0x98;
382 	quote     = 1;
383       }
384     }
385     else
386       *bufptr++ = *idstr;
387 
388     idstr ++;
389   }
390 
391   *bufptr = '\0';
392 
393   return (buffer);
394 }
395