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