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