1 /***********************************************************************
2 *                                                                      *
3 *               This software is part of the ast package               *
4 *          Copyright (c) 2000-2011 AT&T Intellectual Property          *
5 *                      and is licensed under the                       *
6 *                 Eclipse Public License, Version 1.0                  *
7 *                    by AT&T Intellectual Property                     *
8 *                                                                      *
9 *                A copy of the License is available at                 *
10 *          http://www.eclipse.org/org/documents/epl-v10.html           *
11 *         (with md5 checksum b35adb5213ca9657e911e9befb180842)         *
12 *                                                                      *
13 *              Information and Software Systems Research               *
14 *                            AT&T Research                             *
15 *                           Florham Park NJ                            *
16 *                                                                      *
17 *                 Glenn Fowler <gsf@research.att.com>                  *
18 *                                                                      *
19 ***********************************************************************/
20 #pragma prototyped
21 /*
22  * Glenn Fowler
23  * AT&T Research
24  */
25 
26 static const char usage[] =
27 "[-?\n@(#)$Id: msggen (AT&T Research) 2002-03-11 $\n]"
28 USAGE_LICENSE
29 "[+NAME?msggen - generate a machine independent formatted message catalog]"
30 "[+DESCRIPTION?\bmsggen\b merges the message text source files \amsgfile\a"
31 "	into a machine independent formatted message catalog \acatfile\a."
32 "	The file \acatfile\a will be created if it does not already exist."
33 "	If \acatfile\a does exist, its messages will be included in the new"
34 "	\acatfile\a. If set and message numbers collide, the new message"
35 "	text defined in \amsgfile\a will replace the old message text"
36 "	currently contained in \acatfile\a. Non-ASCII characters must be"
37 "	UTF-8 encoded. \biconv\b(1) can be used to convert to/from UTF-8.]"
38 "[f:format?List the \bprintf\b(3) format signature for each message in"
39 "	\acatfile\a. A format signature is one line containing one character"
40 "	per format specification:]{"
41 "		[c?char]"
42 "		[d?double]"
43 "		[D?long double]"
44 "		[f?float]"
45 "		[h?short]"
46 "		[i?int]"
47 "		[j?long long]"
48 "		[l?long]"
49 "		[p?void*]"
50 "		[s?string]"
51 "		[t?ptrdiff_t]"
52 "		[z?size_t]"
53 "		[???unknown]"
54 "}"
55 "[l:list?List \acatfile\a in UTF-8 \amsgfile\a form.]"
56 "[s:set?Convert the \acatfile\a operand to a message set number and"
57 "	print the number on the standard output.]"
58 "[+EXTENDED DESCRIPTION?Message text source files are in \bgencat\b(1)"
59 "	format, defined as follows. Note that the fields of a message text"
60 "	source line are separated by a single blank character. Any other"
61 "	blank characters are considered as being part of the subsequent"
62 "	field. The \bNL_*\b constants are defined in one or both of"
63 "	\b<limits.h>\b and \b<nl_types.h>\b.]{"
64 "		[+$ \acomment\a?A line beginning with \b$\b followed by a"
65 "			blank character is treated as a comment.]"
66 "		[+$delset \an\a \acomment\a?This line deletes message set"
67 "			\an\a from an existing message catalog. \an\a"
68 "			denotes the set number [1, \bNL_SETMAX\b]]. Any"
69 "			text following the set number is treated as a"
70 "			comment.]"
71 "		[+$quote \ac\a?This line specifies an optional quote"
72 "			character \ac\a, which can be used to surround"
73 "			\amessage-text\a so that trailing spaces or"
74 "			empty messages are visible in a message source"
75 "			line. By default, or if an empty \b$quote\b"
76 "			directive is supplied, no quoting of \amessage-text\a"
77 "			will be recognized.]"
78 "		[+$set \an\a \acomment\a?This line specifies the set"
79 "			identifier of the following messages until the next"
80 "			\b$set\b or end-of-file appears. \an\a denotes the set"
81 "			identifier, which is defined as a number in the range"
82 "			[1, \bNL_SETMAX\b]]. Set numbers need not be"
83 "			contiguous. Any text following the set identifier is"
84 "			treated as a comment. If no \b$set\b directive is"
85 "			specified in a 	message text source file, all messages"
86 "			will be located in message set \b1\b.]"
87 "		[+$translation \aidentification\a \aYYYY-MM-DD\a[,...]]?Append"
88 "			translation info to the message catalog header. Only"
89 "			the newest date for a given \aidentification\a"
90 "			is retained in the catalog. Multiple translation lines"
91 "			are combined into a single \b,\b separated list.]"
92 "		[+\am\a \amessage-text\a?\am\a denotes the message identifier,"
93 "			which is defined as a number in the range"
94 "			[1, \bNL_MSGMAX\b]]. The message-text is stored in the"
95 "			message catalogue with the set identifier specified by"
96 "			the last \b$set\b directive, and with message"
97 "			identifier \am\a. If the \amessage-text\a is empty,"
98 "			and a blank character field separator is present, an"
99 "			empty string is stored in the message catalogue. If a"
100 "			message source line has a message number, but neither"
101 "			a field separator nor \amessage-text\a, the existing"
102 "			message with that number (if any) is deleted from the"
103 "			catalogue. Message identifiers need not be contiguous."
104 "			There are no \amessage-text\a length restrictions.]"
105 "}"
106 
107 "\n"
108 "\ncatfile [ msgfile ]\n"
109 "\n"
110 
111 "[+SEE ALSO?\bgencat\b(1), \biconv\b(1), \bmsgcc\b(1), \btranslate\b(1),"
112 "	\bfmtfmt\b(3)]"
113 ;
114 
115 #include <ast.h>
116 #include <ctype.h>
117 #include <ccode.h>
118 #include <error.h>
119 #include <mc.h>
120 
121 typedef struct Xl_s
122 {
123 	struct Xl_s*	next;
124 	char*		date;
125 	char		name[1];
126 } Xl_t;
127 
128 /*
129  * append s to the translation list
130  */
131 
132 static Xl_t*
translation(Xl_t * xp,register char * s)133 translation(Xl_t* xp, register char* s)
134 {
135 	register Xl_t*	px;
136 	register char*	t;
137 	char*		d;
138 	char*		e;
139 
140 	do
141 	{
142 		for (; isspace(*s); s++);
143 		for (d = e = 0, t = s; *t; t++)
144 			if (*t == ',')
145 			{
146 				e = t;
147 				*e++ = 0;
148 				break;
149 			}
150 			else if (isspace(*t))
151 				d = t;
152 		if (d)
153 		{
154 			*d++ = 0;
155 			for (px = xp; px; px = px->next)
156 				if (streq(px->name, s))
157 				{
158 					if (strcoll(px->date, d) < 0)
159 					{
160 						free(px->date);
161 						if (!(px->date = strdup(d)))
162 							error(ERROR_SYSTEM|3, "out of space [translation]");
163 					}
164 					break;
165 				}
166 			if (!px)
167 			{
168 				if (!(px = newof(0, Xl_t, 1, strlen(s))) || !(px->date = strdup(d)))
169 					error(ERROR_SYSTEM|3, "out of space [translation]");
170 				strcpy(px->name, s);
171 				px->next = xp;
172 				xp = px;
173 			}
174 		}
175 	} while (s = e);
176 	return xp;
177 }
178 
179 /*
180  * sfprintf() with ccmaps(from,to)
181  */
182 
183 static int
ccsfprintf(int from,int to,Sfio_t * sp,const char * format,...)184 ccsfprintf(int from, int to, Sfio_t* sp, const char* format, ...)
185 {
186 	va_list		ap;
187 	Sfio_t*		tp;
188 	char*		s;
189 	int		n;
190 
191 	va_start(ap, format);
192 	if (from == to)
193 		n = sfvprintf(sp, format, ap);
194 	else if (tp = sfstropen())
195 	{
196 		n = sfvprintf(tp, format, ap);
197 		s = sfstrbase(tp);
198 		ccmaps(s, n, from, to);
199 		n = sfwrite(sp, s, n);
200 		sfstrclose(tp);
201 	}
202 	else
203 		n = -1;
204 	return n;
205 }
206 
207 int
main(int argc,char ** argv)208 main(int argc, char** argv)
209 {
210 	register Mc_t*	mc;
211 	register char*	s;
212 	register char*	t;
213 	register int	c;
214 	register int	q;
215 	register int	i;
216 	int		num;
217 	char*		b;
218 	char*		e;
219 	char*		catfile;
220 	char*		msgfile;
221 	Sfio_t*		sp;
222 	Sfio_t*		mp;
223 	Sfio_t*		tp;
224 	Xl_t*		px;
225 	Xl_t*		bp;
226 
227 	Xl_t*		xp = 0;
228 	int		format = 0;
229 	int		list = 0;
230 	int		set = 0;
231 
232 	NoP(argc);
233 	error_info.id = "msggen";
234 	for (;;)
235 	{
236 		switch (optget(argv, usage))
237 		{
238 		case 'f':
239 			format = list = 1;
240 			continue;
241 		case 'l':
242 			list = 1;
243 			continue;
244 		case 's':
245 			set = 1;
246 			continue;
247 		case '?':
248 			error(ERROR_USAGE|4, "%s", opt_info.arg);
249 			continue;
250 		case ':':
251 			error(2, "%s", opt_info.arg);
252 			continue;
253 		}
254 		break;
255 	}
256 	argv += opt_info.index;
257 	if (error_info.errors || !(catfile = *argv++))
258 		error(ERROR_USAGE|4, "%s", optusage(NiL));
259 
260 	/*
261 	 * set and list only need catfile
262 	 */
263 
264 	if (set)
265 	{
266 		sfprintf(sfstdout, "%d\n", mcindex(catfile, NiL, NiL, NiL));
267 		return error_info.errors != 0;
268 	}
269 	else if (list)
270 	{
271 		if (!(sp = sfopen(NiL, catfile, "r")))
272 			error(ERROR_SYSTEM|3, "%s: cannot read catalog", catfile);
273 		if (!(mc = mcopen(sp)))
274 			error(ERROR_SYSTEM|3, "%s: catalog content error", catfile);
275 		sfclose(sp);
276 		if (format)
277 		{
278 			for (set = 1; set <= mc->num; set++)
279 				if (mc->set[set].num)
280 				{
281 					sfprintf(sfstdout, "$set %d\n", set);
282 					for (num = 1; num <= mc->set[set].num; num++)
283 						if (s = mc->set[set].msg[num])
284 							sfprintf(sfstdout, "%d \"%s\"\n", num, fmtfmt(s));
285 				}
286 		}
287 		else
288 		{
289 			if (*mc->translation)
290 			{
291 				ccsfprintf(CC_NATIVE, CC_ASCII, sfstdout, "$translation ");
292 				sfprintf(sfstdout, "%s", mc->translation);
293 				ccsfprintf(CC_NATIVE, CC_ASCII, sfstdout, "\n");
294 			}
295 			ccsfprintf(CC_NATIVE, CC_ASCII, sfstdout, "$quote \"\n");
296 			for (set = 1; set <= mc->num; set++)
297 				if (mc->set[set].num)
298 				{
299 					ccsfprintf(CC_NATIVE, CC_ASCII, sfstdout, "$set %d\n", set);
300 					for (num = 1; num <= mc->set[set].num; num++)
301 						if (s = mc->set[set].msg[num])
302 						{
303 							ccsfprintf(CC_NATIVE, CC_ASCII, sfstdout, "%d \"", num);
304 							while (c = *s++)
305 							{
306 								/*INDENT...*/
307 
308 			switch (c)
309 			{
310 			case 0x22: /* " */
311 			case 0x5C: /* \ */
312 				sfputc(sfstdout, 0x5C);
313 				break;
314 			case 0x07: /* \a */
315 				c = 0x61;
316 				sfputc(sfstdout, 0x5C);
317 				break;
318 			case 0x08: /* \b */
319 				c = 0x62;
320 				sfputc(sfstdout, 0x5C);
321 				break;
322 			case 0x0A: /* \n */
323 				c = 0x6E;
324 				sfputc(sfstdout, 0x5C);
325 				break;
326 			case 0x0B: /* \v */
327 				c = 0x76;
328 				sfputc(sfstdout, 0x5C);
329 				break;
330 			case 0x0C: /* \f */
331 				c = 0x66;
332 				sfputc(sfstdout, 0x5C);
333 				break;
334 			case 0x0D: /* \r */
335 				c = 0x72;
336 				sfputc(sfstdout, 0x5C);
337 				break;
338 			}
339 
340 								/*...UNDENT*/
341 								sfputc(sfstdout, c);
342 							}
343 							ccsfprintf(CC_NATIVE, CC_ASCII, sfstdout, "\"\n");
344 						}
345 				}
346 		}
347 		mcclose(mc);
348 		return error_info.errors != 0;
349 	}
350 	else if (!(msgfile = *argv++) || *argv)
351 		error(3, "exactly one message file must be specified");
352 
353 	/*
354 	 * open the files and handles
355 	 */
356 
357 	if (!(tp = sfstropen()))
358 		error(ERROR_SYSTEM|3, "out of space [string stream]");
359 	if (!(mp = sfopen(NiL, msgfile, "r")))
360 		error(ERROR_SYSTEM|3, "%s: cannot read message file", msgfile);
361 	sp = sfopen(NiL, catfile, "r");
362 	if (!(mc = mcopen(sp)))
363 		error(ERROR_SYSTEM|3, "%s: catalog content error", catfile);
364 	if (sp)
365 		sfclose(sp);
366 	xp = translation(xp, mc->translation);
367 
368 	/*
369 	 * read the message file
370 	 */
371 
372 	q = 0;
373 	set = 1;
374 	error_info.file = msgfile;
375 	while (s = sfgetr(mp, '\n', 1))
376 	{
377 		error_info.line++;
378 		if (!*s)
379 			continue;
380 		if (*s == '$')
381 		{
382 			if (!*++s || isspace(*s))
383 				continue;
384 			for (t = s; *s && !isspace(*s); s++);
385 			if (*s)
386 				*s++ = 0;
387 			if (streq(t, "delset"))
388 			{
389 				while (isspace(*s))
390 					s++;
391 				num = (int)strtol(s, NiL, 0);
392 				if (num < mc->num && mc->set[num].num)
393 					for (i = 1; i <= mc->set[num].num; i++)
394 						mcput(mc, num, i, NiL);
395 			}
396 			else if (streq(t, "quote"))
397 				q = *s ? *s : 0;
398 			else if (streq(t, "set"))
399 			{
400 				while (isspace(*s))
401 					s++;
402 				num = (int)strtol(s, &e, 0);
403 				if (e != s)
404 					set = num;
405 				else
406 					error(2, "set number expected");
407 			}
408 			else if (streq(t, "translation"))
409 				xp = translation(xp, s);
410 		}
411 		else
412 		{
413 			t = s + sfvalue(mp);
414 			num = (int)strtol(s, &e, 0);
415 			if (e != s)
416 			{
417 				s = e;
418 				if (!*s)
419 				{
420 					if (mcput(mc, set, num, NiL))
421 						error(2, "(%d,%d): cannot delete message", set, num);
422 				}
423 				else if (isspace(*s++))
424 				{
425 					if (t > (s + 1) && *(t -= 2) == '\\')
426 					{
427 						sfwrite(tp, s, t - s);
428 						while (s = sfgetr(mp, '\n', 0))
429 						{
430 							error_info.line++;
431 							t = s + sfvalue(mp);
432 							if (t <= (s + 1) || *(t -= 2) != '\\')
433 								break;
434 							sfwrite(tp, s, t - s);
435 						}
436 						if (!(s = sfstruse(tp)))
437 							error(ERROR_SYSTEM|3, "out of space");
438 					}
439 					if (q)
440 					{
441 						if (*s++ != q)
442 						{
443 							error(2, "(%d,%d): %c quote expected", set, num, q);
444 							continue;
445 						}
446 						b = t = s;
447 						while (c = *s++)
448 						{
449 							if (c == '\\')
450 							{
451 								c = chresc(s - 1, &e);
452 								s = e;
453 								if (c)
454 									*t++ = c;
455 								else
456 									error(1, "nul character ignored");
457 							}
458 							else if (c == q)
459 								break;
460 							else
461 								*t++ = c;
462 						}
463 						if (*s)
464 						{
465 							error(2, "(%d,%d): characters after quote not expected", set, num);
466 							continue;
467 						}
468 						*t = 0;
469 						s = b;
470 					}
471 					if (mcput(mc, set, num, s))
472 						error(2, "(%d,%d): cannot add message", set, num);
473 				}
474 				else
475 					error(2, "message text expected");
476 			}
477 			else
478 				error(2, "message number expected");
479 		}
480 	}
481 	error_info.file = 0;
482 	error_info.line = 0;
483 
484 	/*
485 	 * fix up the translation record
486 	 */
487 
488 	if (xp)
489 	{
490 		t = "";
491 		for (;;)
492 		{
493 			for (bp = 0, px = xp; px; px = px->next)
494 				if (px->date && (!bp || strcoll(bp->date, px->date) < 0))
495 					bp = px;
496 			if (!bp)
497 				break;
498 			sfprintf(tp, "%s%s %s", t, bp->name, bp->date);
499 			t = ", ";
500 			bp->date = 0;
501 		}
502 		if (!(mc->translation = sfstruse(tp)))
503 			error(ERROR_SYSTEM|3, "out of space");
504 	}
505 
506 	/*
507 	 * dump the catalog to a local temporary
508 	 * rename if no errors
509 	 */
510 
511 	if (!(s = pathtemp(NiL, 0, "", error_info.id, NiL)) || !(sp = sfopen(NiL, s, "w")))
512 		error(ERROR_SYSTEM|3, "%s: cannot write catalog file", catfile);
513 	if (mcdump(mc, sp) || mcclose(mc) || sfclose(sp))
514 	{
515 		remove(s);
516 		error(ERROR_SYSTEM|3, "%s: temporary catalog file write error", s);
517 	}
518 	remove(catfile);
519 	if (rename(s, catfile))
520 		error(ERROR_SYSTEM|3, "%s: cannot rename from temporary catalog file %s", catfile, s);
521 	return error_info.errors != 0;
522 }
523