/*********************************************************************** * * * This software is part of the ast package * * Copyright (c) 2000-2007 AT&T Knowledge Ventures * * and is licensed under the * * Common Public License, Version 1.0 * * by AT&T Knowledge Ventures * * * * A copy of the License is available at * * http://www.opensource.org/licenses/cpl1.0.txt * * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * * * * Information and Software Systems Research * * AT&T Research * * Florham Park NJ * * * * Glenn Fowler * * * ***********************************************************************/ #pragma prototyped /* * Glenn Fowler * AT&T Research */ static const char usage[] = "[-?\n@(#)$Id: msggen (AT&T Research) 2002-03-11 $\n]" USAGE_LICENSE "[+NAME?msggen - generate a machine independent formatted message catalog]" "[+DESCRIPTION?\bmsggen\b merges the message text source files \amsgfile\a" " into a machine independent formatted message catalog \acatfile\a." " The file \acatfile\a will be created if it does not already exist." " If \acatfile\a does exist, its messages will be included in the new" " \acatfile\a. If set and message numbers collide, the new message" " text defined in \amsgfile\a will replace the old message text" " currently contained in \acatfile\a. Non-ASCII characters must be" " UTF-8 encoded. \biconv\b(1) can be used to convert to/from UTF-8.]" "[f:format?List the \bprintf\b(3) format signature for each message in" " \acatfile\a. A format signature is one line containing one character" " per format specification:]{" " [c?char]" " [d?double]" " [D?long double]" " [f?float]" " [h?short]" " [i?int]" " [j?long long]" " [l?long]" " [p?void*]" " [s?string]" " [t?ptrdiff_t]" " [z?size_t]" " [???unknown]" "}" "[l:list?List \acatfile\a in UTF-8 \amsgfile\a form.]" "[s:set?Convert the \acatfile\a operand to a message set number and" " print the number on the standard output.]" "[+EXTENDED DESCRIPTION?Message text source files are in \bgencat\b(1)" " format, defined as follows. Note that the fields of a message text" " source line are separated by a single blank character. Any other" " blank characters are considered as being part of the subsequent" " field. The \bNL_*\b constants are defined in one or both of" " \b\b and \b\b.]{" " [+$ \acomment\a?A line beginning with \b$\b followed by a" " blank character is treated as a comment.]" " [+$delset \an\a \acomment\a?This line deletes message set" " \an\a from an existing message catalog. \an\a" " denotes the set number [1, \bNL_SETMAX\b]]. Any" " text following the set number is treated as a" " comment.]" " [+$quote \ac\a?This line specifies an optional quote" " character \ac\a, which can be used to surround" " \amessage-text\a so that trailing spaces or" " empty messages are visible in a message source" " line. By default, or if an empty \b$quote\b" " directive is supplied, no quoting of \amessage-text\a" " will be recognized.]" " [+$set \an\a \acomment\a?This line specifies the set" " identifier of the following messages until the next" " \b$set\b or end-of-file appears. \an\a denotes the set" " identifier, which is defined as a number in the range" " [1, \bNL_SETMAX\b]]. Set numbers need not be" " contiguous. Any text following the set identifier is" " treated as a comment. If no \b$set\b directive is" " specified in a message text source file, all messages" " will be located in message set \b1\b.]" " [+$translation \aidentification\a \aYYYY-MM-DD\a[,...]]?Append" " translation info to the message catalog header. Only" " the newest date for a given \aidentification\a" " is retained in the catalog. Multiple translation lines" " are combined into a single \b,\b separated list.]" " [+\am\a \amessage-text\a?\am\a denotes the message identifier," " which is defined as a number in the range" " [1, \bNL_MSGMAX\b]]. The message-text is stored in the" " message catalogue with the set identifier specified by" " the last \b$set\b directive, and with message" " identifier \am\a. If the \amessage-text\a is empty," " and a blank character field separator is present, an" " empty string is stored in the message catalogue. If a" " message source line has a message number, but neither" " a field separator nor \amessage-text\a, the existing" " message with that number (if any) is deleted from the" " catalogue. Message identifiers need not be contiguous." " There are no \amessage-text\a length restrictions.]" "}" "\n" "\ncatfile [ msgfile ]\n" "\n" "[+SEE ALSO?\bgencat\b(1), \biconv\b(1), \bmsgcc\b(1), \btranslate\b(1)," " \bfmtfmt\b(3)]" ; #include #include #include #include #include typedef struct Xl_s { struct Xl_s* next; char* date; char name[1]; } Xl_t; /* * append s to the translation list */ static Xl_t* translation(Xl_t* xp, register char* s) { register Xl_t* px; register char* t; char* d; char* e; do { for (; isspace(*s); s++); for (d = e = 0, t = s; *t; t++) if (*t == ',') { e = t; *e++ = 0; break; } else if (isspace(*t)) d = t; if (d) { *d++ = 0; for (px = xp; px; px = px->next) if (streq(px->name, s)) { if (strcoll(px->date, d) < 0) { free(px->date); if (!(px->date = strdup(d))) error(ERROR_SYSTEM|3, "out of space [translation]"); } break; } if (!px) { if (!(px = newof(0, Xl_t, 1, strlen(s))) || !(px->date = strdup(d))) error(ERROR_SYSTEM|3, "out of space [translation]"); strcpy(px->name, s); px->next = xp; xp = px; } } } while (s = e); return xp; } /* * sfprintf() with ccmaps(from,to) */ static int ccsfprintf(int from, int to, Sfio_t* sp, const char* format, ...) { va_list ap; Sfio_t* tp; char* s; int n; va_start(ap, format); if (from == to) n = sfvprintf(sp, format, ap); else if (tp = sfstropen()) { n = sfvprintf(tp, format, ap); s = sfstrbase(tp); ccmaps(s, n, from, to); n = sfwrite(sp, s, n); sfstrclose(tp); } else n = -1; return n; } int main(int argc, char** argv) { register Mc_t* mc; register char* s; register char* t; register int c; register int q; register int i; int num; char* b; char* e; char* catfile; char* msgfile; Sfio_t* sp; Sfio_t* mp; Sfio_t* tp; Xl_t* px; Xl_t* bp; Xl_t* xp = 0; int format = 0; int list = 0; int set = 0; NoP(argc); error_info.id = "msggen"; for (;;) { switch (optget(argv, usage)) { case 'f': format = list = 1; continue; case 'l': list = 1; continue; case 's': set = 1; continue; case '?': error(ERROR_USAGE|4, "%s", opt_info.arg); continue; case ':': error(2, "%s", opt_info.arg); continue; } break; } argv += opt_info.index; if (error_info.errors || !(catfile = *argv++)) error(ERROR_USAGE|4, "%s", optusage(NiL)); /* * set and list only need catfile */ if (set) { sfprintf(sfstdout, "%d\n", mcindex(catfile, NiL, NiL, NiL)); return error_info.errors != 0; } else if (list) { if (!(sp = sfopen(NiL, catfile, "r"))) error(ERROR_SYSTEM|3, "%s: cannot read catalog", catfile); if (!(mc = mcopen(sp))) error(ERROR_SYSTEM|3, "%s: catalog content error", catfile); sfclose(sp); if (format) { for (set = 1; set <= mc->num; set++) if (mc->set[set].num) { sfprintf(sfstdout, "$set %d\n", set); for (num = 1; num <= mc->set[set].num; num++) if (s = mc->set[set].msg[num]) sfprintf(sfstdout, "%d \"%s\"\n", num, fmtfmt(s)); } } else { if (*mc->translation) { ccsfprintf(CC_NATIVE, CC_ASCII, sfstdout, "$translation "); sfprintf(sfstdout, "%s", mc->translation); ccsfprintf(CC_NATIVE, CC_ASCII, sfstdout, "\n"); } ccsfprintf(CC_NATIVE, CC_ASCII, sfstdout, "$quote \"\n"); for (set = 1; set <= mc->num; set++) if (mc->set[set].num) { ccsfprintf(CC_NATIVE, CC_ASCII, sfstdout, "$set %d\n", set); for (num = 1; num <= mc->set[set].num; num++) if (s = mc->set[set].msg[num]) { ccsfprintf(CC_NATIVE, CC_ASCII, sfstdout, "%d \"", num); while (c = *s++) { /*INDENT...*/ switch (c) { case 0x22: /* " */ case 0x5C: /* \ */ sfputc(sfstdout, 0x5C); break; case 0x07: /* \a */ c = 0x61; sfputc(sfstdout, 0x5C); break; case 0x08: /* \b */ c = 0x62; sfputc(sfstdout, 0x5C); break; case 0x0A: /* \n */ c = 0x6E; sfputc(sfstdout, 0x5C); break; case 0x0B: /* \v */ c = 0x76; sfputc(sfstdout, 0x5C); break; case 0x0C: /* \f */ c = 0x66; sfputc(sfstdout, 0x5C); break; case 0x0D: /* \r */ c = 0x72; sfputc(sfstdout, 0x5C); break; } /*...UNDENT*/ sfputc(sfstdout, c); } ccsfprintf(CC_NATIVE, CC_ASCII, sfstdout, "\"\n"); } } } mcclose(mc); return error_info.errors != 0; } else if (!(msgfile = *argv++) || *argv) error(3, "exactly one message file must be specified"); /* * open the files and handles */ if (!(tp = sfstropen())) error(ERROR_SYSTEM|3, "out of space [string stream]"); if (!(mp = sfopen(NiL, msgfile, "r"))) error(ERROR_SYSTEM|3, "%s: cannot read message file", msgfile); sp = sfopen(NiL, catfile, "r"); if (!(mc = mcopen(sp))) error(ERROR_SYSTEM|3, "%s: catalog content error", catfile); if (sp) sfclose(sp); xp = translation(xp, mc->translation); /* * read the message file */ q = 0; set = 1; error_info.file = msgfile; while (s = sfgetr(mp, '\n', 1)) { error_info.line++; if (!*s) continue; if (*s == '$') { if (!*++s || isspace(*s)) continue; for (t = s; *s && !isspace(*s); s++); if (*s) *s++ = 0; if (streq(t, "delset")) { while (isspace(*s)) s++; num = (int)strtol(s, NiL, 0); if (num < mc->num && mc->set[num].num) for (i = 1; i <= mc->set[num].num; i++) mcput(mc, num, i, NiL); } else if (streq(t, "quote")) q = *s ? *s : 0; else if (streq(t, "set")) { while (isspace(*s)) s++; num = (int)strtol(s, &e, 0); if (e != s) set = num; else error(2, "set number expected"); } else if (streq(t, "translation")) xp = translation(xp, s); } else { t = s + sfvalue(mp); num = (int)strtol(s, &e, 0); if (e != s) { s = e; if (!*s) { if (mcput(mc, set, num, NiL)) error(2, "(%d,%d): cannot delete message", set, num); } else if (isspace(*s++)) { if (t > (s + 1) && *(t -= 2) == '\\') { sfwrite(tp, s, t - s); while (s = sfgetr(mp, '\n', 0)) { error_info.line++; t = s + sfvalue(mp); if (t <= (s + 1) || *(t -= 2) != '\\') break; sfwrite(tp, s, t - s); } if (!(s = sfstruse(tp))) error(ERROR_SYSTEM|3, "out of space"); } if (q) { if (*s++ != q) { error(2, "(%d,%d): %c quote expected", set, num, q); continue; } b = t = s; while (c = *s++) { if (c == '\\') { c = chresc(s - 1, &e); s = e; if (c) *t++ = c; else error(1, "nul character ignored"); } else if (c == q) break; else *t++ = c; } if (*s) { error(2, "(%d,%d): characters after quote not expected", set, num); continue; } *t = 0; s = b; } if (mcput(mc, set, num, s)) error(2, "(%d,%d): cannot add message", set, num); } else error(2, "message text expected"); } else error(2, "message number expected"); } } error_info.file = 0; error_info.line = 0; /* * fix up the translation record */ if (xp) { t = ""; for (;;) { for (bp = 0, px = xp; px; px = px->next) if (px->date && (!bp || strcoll(bp->date, px->date) < 0)) bp = px; if (!bp) break; sfprintf(tp, "%s%s %s", t, bp->name, bp->date); t = ", "; bp->date = 0; } if (!(mc->translation = sfstruse(tp))) error(ERROR_SYSTEM|3, "out of space"); } /* * dump the catalog to a local temporary * rename if no errors */ if (!(s = pathtemp(NiL, 0, "", error_info.id, NiL)) || !(sp = sfopen(NiL, s, "w"))) error(ERROR_SYSTEM|3, "%s: cannot write catalog file", catfile); if (mcdump(mc, sp) || mcclose(mc) || sfclose(sp)) { remove(s); error(ERROR_SYSTEM|3, "%s: temporary catalog file write error", s); } remove(catfile); if (rename(s, catfile)) error(ERROR_SYSTEM|3, "%s: cannot rename from temporary catalog file %s", catfile, s); return error_info.errors != 0; }