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