1 /*********************************************************************** 2 * * 3 * This software is part of the ast package * 4 * Copyright (c) 2000-2009 AT&T Intellectual Property * 5 * and is licensed under the * 6 * Common Public License, Version 1.0 * 7 * by AT&T Intellectual Property * 8 * * 9 * A copy of the License is available at * 10 * http://www.opensource.org/licenses/cpl1.0.txt * 11 * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * 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* 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 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 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