/* * CDE - Common Desktop Environment * * Copyright (c) 1993-2012, The Open Group. All rights reserved. * * These libraries and programs are free software; you can * redistribute them and/or modify them under the terms of the GNU * Lesser General Public License as published by the Free Software * Foundation; either version 2 of the License, or (at your option) * any later version. * * These libraries and programs are distributed in the hope that * they will be useful, but WITHOUT ANY WARRANTY; without even the * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU Lesser General Public License for more * details. * * You should have received a copy of the GNU Lesser General Public * License along with these librararies and programs; if not, write * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth * Floor, Boston, MA 02110-1301 USA */ /* $XConsortium: msgcat.c /main/3 1996/06/19 17:16:22 drk $ */ /* msgcat.c - X/Open message catalogue functions and gencat utility. Written by James Clark (jjc@jclark.com). */ #include "config.h" #ifndef HAVE_CAT /* In this implementation the message catalogue format is the same as the message text source file format (see pp 42-43 of the X/Open Portability Guide, Issue 3, Volume 3.) This means that you don't have to use the gencat utility, but it is still useful for checking and merging catalogues. */ /* Compile this with -DGENCAT to get the gencat utility. */ #include "std.h" #include "msgcat.h" #ifdef USE_PROTOTYPES #define P(parms) parms #else #define P(parms) () #endif /* Default message set. */ #define NL_SETD 1 #ifndef PATH_FILE_SEP #define PATH_FILE_SEP ':' #endif #ifndef DEFAULT_NLSPATH #define DEFAULT_NLSPATH "" #endif #ifndef DEFAULT_LANG #define DEFAULT_LANG "default" #endif #define HASH_TAB_SIZE 251 struct message { struct message *next; unsigned msgnum; unsigned setnum; char *text; }; struct cat { char *name; int loaded; int bad; struct message *table[HASH_TAB_SIZE]; }; static char *read_buf = 0; static unsigned read_buf_len = 0; /* Errors that can be generated by read_catalog. */ enum cat_err { E_ZERO, /* not an error */ E_BADARG, E_NOMEM, E_NOSUCHCOMMAND, E_INPUT, E_EOF, E_BADSEP, E_BADLINE }; #ifdef GENCAT /* These must match enum cat_err. */ static char *cat_errlist[] = { "Error 0", "Invalid argument to command", "Out of memory", "Unrecognized command", "Input error", "Unexpected end of file", "Space or tab expected after message number", "Invalid line", }; #endif /* GENCAT */ #ifndef GENCAT /* The value of NLSPATH. */ static char *nlspath = 0; /* The value of LANG. */ static char *lang = 0; #endif /* not GENCAT */ static int current_lineno = -1; static enum cat_err cat_errno = E_ZERO; #ifndef GENCAT static void load_catalog P((struct cat *)); static FILE *find_catalog P((char *, char **)); #endif static int read_catalog P((FILE *, struct message **)); static void delete_set P((struct message **, unsigned)); static void delete_message P((struct message **, unsigned, unsigned)); static int hash P((unsigned setnum, unsigned msgnum)); static char *parse_text P((FILE *, int)); #ifndef GENCAT nl_catd catopen(name, oflag) char *name; int oflag; { struct cat *catp; int i; if (!name) return 0; catp = (struct cat *)malloc(sizeof *catp); if (!catp) return 0; for (i = 0; i < HASH_TAB_SIZE; i++) catp->table[i] = 0; catp->name = malloc(strlen(name) + 1); catp->loaded = 0; catp->bad = 0; strcpy(catp->name, name); return (nl_catd)catp; } int catclose(catd) nl_catd catd; { int i; struct cat *catp = (struct cat *)catd; if (!catp) return 0; for (i = 0; i < HASH_TAB_SIZE; i++) { struct message *p, *nextp; for (p = catp->table[i]; p; p = nextp) { nextp = p->next; free(p->text); free((char *)p); } } if (catp->name) free(catp->name); free((char *)catp); return 0; } char *catgets(catd, setnum, msgnum, dflt) nl_catd catd; int setnum, msgnum; char *dflt; { struct message *p; struct cat *catp; /* setnum and msgnum are required to be >= 1. */ if (!catd || setnum <= 0 || msgnum <= 0) return dflt; catp = (struct cat *)catd; if (!catp->loaded) load_catalog(catp); if (catp->bad) return dflt; for (p = catp->table[hash(setnum, msgnum)]; p; p = p->next) if (p->msgnum == msgnum && p->setnum == setnum) break; if (!p) return dflt; return p->text; } static VOID load_catalog(catp) struct cat *catp; { FILE *fp; char *path; catp->loaded = 1; fp = find_catalog(catp->name, &path); if (!fp) { catp->bad = 1; return; } current_lineno = 0; if (read_catalog(fp, catp->table) < 0) catp->bad = 1; fclose(fp); if (read_buf) { free(read_buf); read_buf = 0; } read_buf_len = 0; free(path); } static FILE *find_catalog(name, pathp) char *name; char **pathp; { char *path; if (!name) return 0; if (!nlspath) { nlspath = getenv("NLSPATH"); if (!nlspath) nlspath = DEFAULT_NLSPATH; } if (!lang) { lang = getenv("LANG"); if (!lang) lang = DEFAULT_LANG; } path = nlspath; for (;;) { char *p; unsigned len = 0; for (p = path; *p != '\0' && *p != PATH_FILE_SEP; p++) { if (*p == '%') { if (p[1] == 'N') { p++; len += strlen(name); } else if (p[1] == 'L') { p++; len += strlen(lang); } else if (p[1] == '%') { p++; len++; } else len++; } else len++; } if (len > 0) { char *s, *try; FILE *fp; s = try = malloc(len + 1); if (!s) return 0; for (p = path; *p != '\0' && *p != PATH_FILE_SEP; p++) { if (*p == '%') { if (p[1] == 'N') { p++; strcpy(s, name); s += strlen(name); } else if (p[1] == 'L') { p++; strcpy(s, lang); s += strlen(lang); } else if (p[1] == '%') { p++; *s++ = '%'; } else *s++ = *p; } else *s++ = *p; } *s++ = '\0'; fp = fopen(try, "r"); if (fp) { *pathp = try; return fp; } free(try); } if (*p == '\0') break; path = ++p; } return 0; } #endif /* not GENCAT */ /* 0 success, -1 error */ static int parse_message(c, fp, table, setnum, quote) int c; FILE *fp; struct message **table; unsigned setnum; int quote; { unsigned msgnum; struct message *msgp; char *text; int hc; msgnum = c - '0'; for (;;) { c = getc(fp); if (!isdigit(c)) break; msgnum = msgnum*10 + (c - '0'); } if (c == '\n') { delete_message(table, setnum, msgnum); return 0; } if (c != ' ' && c != '\t') { cat_errno = E_BADSEP; return -1; } text = parse_text(fp, quote); if (!text) return -1; hc = hash(setnum, msgnum); for (msgp = table[hc]; msgp; msgp = msgp->next) if (msgp->setnum == setnum && msgp->msgnum == msgnum) break; if (msgp) free(msgp->text); else { msgp = (struct message *)malloc(sizeof *msgp); if (!msgp) { cat_errno = E_NOMEM; return -1; } msgp->next = table[hc]; table[hc] = msgp; msgp->msgnum = msgnum; msgp->setnum = setnum; } msgp->text = text; return 0; } static char *parse_text(fp, quote) FILE *fp; int quote; { unsigned i = 0; char *p; int c; int quoted; c = getc(fp); if (c == quote) { quoted = 1; c = getc(fp); } else quoted = 0; for (;; c = getc(fp)) { if (c == EOF) { if (ferror(fp)) { cat_errno = E_INPUT; return 0; } break; } if (c == '\n') break; /* XXX Can quotes be used in quoted message text if protected by \ ? Is it illegal to omit the closing quote if there's an opening quote? Is it illegal to have anything after a closing quote? */ if (quoted && c == quote) { /* Skip the rest of the line. */ while ((c = getc(fp)) != '\n') if (c == EOF) { if (ferror(fp)) { cat_errno = E_INPUT; return 0; } break; } break; } if (c == '\\') { int d; c = getc(fp); if (c == EOF) break; switch (c) { case '\n': current_lineno++; continue; case 'n': c = '\n'; break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case 'r': c = '\r'; break; case '\\': c = '\\'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': c -= '0'; d = getc(fp); if (d >= '0' && d <= '7') { c = c*8 + d - '0'; d = getc(fp); if (d >= '0' && d <= '7') c = c*8 + d - '0'; else if (d != EOF) ungetc(d,fp); } else if (d != EOF) ungetc(d, fp); if (c == '\0') continue; /* XXX */ break; default: /* Ignore the quote. */ break; } } if (i >= read_buf_len) { if (!read_buf) read_buf = malloc(read_buf_len = 40); else read_buf = realloc(read_buf, read_buf_len *= 2); if (!read_buf) { cat_errno = E_NOMEM; return 0; } } read_buf[i++] = c; } p = malloc(i + 1); if (!p) { cat_errno = E_NOMEM; return 0; } memcpy(p, read_buf, i); p[i] = '\0'; return p; } /* 0 success, -1 error */ static int parse_command(fp, table, setnump, quotep) FILE *fp; struct message **table; unsigned *setnump; int *quotep; { char buf[128]; if (fgets(buf, 128, fp) == NULL) { cat_errno = ferror(fp) ? E_INPUT : E_EOF; return -1; } if (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\n') /* a comment */; else if (strncmp(buf, "set", 3) == 0) { if (sscanf(buf + 3, "%u", setnump) != 1) { cat_errno = E_BADARG; return -1; } } else if (strncmp(buf, "delset", 6) == 0) { unsigned num; if (sscanf(buf + 6, "%u", &num) != 1) { cat_errno = E_BADARG; return -1; } delete_set(table, num); *setnump = NL_SETD; } else if (strncmp(buf, "quote", 5) == 0) { char *p = buf + 5; while (*p == ' ' || *p == '\t') p++; /* XXX should \ be allowed as the quote character? */ if (*p == '\0' || *p == '\n') *quotep = -1; else *quotep = *p; } else { cat_errno = E_NOSUCHCOMMAND; return -1; } if (strchr(buf, '\n') == 0) { int c; while ((c = getc(fp)) != '\n' && c != EOF) ; } return 0; } static VOID delete_set(table, setnum) struct message **table; unsigned setnum; { int i; for (i = 0; i < HASH_TAB_SIZE; i++) { struct message *p, *nextp; for (p = table[i], table[i] = 0; p; p = nextp) { nextp = p->next; if (p->setnum == setnum) free((char *)p); else { p->next = table[i]; table[i] = p; } } } } static VOID delete_message(table, setnum, msgnum) struct message **table; unsigned setnum, msgnum; { struct message **pp; for (pp = &table[hash(setnum, msgnum)]; *pp; pp = &(*pp)->next) if ((*pp)->setnum == setnum && (*pp)->msgnum == msgnum) { struct message *p = *pp; *pp = p->next; free(p->text); free((char *)p); break; } } /* 0 success, -1 error. On error cat_errno is set to the error number. */ static int read_catalog(fp, table) FILE *fp; struct message **table; { int c; unsigned setnum = NL_SETD; int quote_char = -1; for (;;) { /* start of line */ c = getc(fp); if (c == EOF) break; ++current_lineno; if (isdigit(c)) { if (parse_message(c, fp, table, setnum, quote_char) < 0) return -1; } else if (c == '$') { if (parse_command(fp, table, &setnum, "e_char) < 0) return -1; } else if (c != '\n') { while ((c = getc(fp)) != '\n' && c != EOF) if (c != ' ' && c != '\t') { cat_errno = E_BADLINE; return -1; } if (c == EOF) break; } } return 0; } static int hash(setnum, msgnum) unsigned setnum, msgnum; { return ((setnum << 8) + msgnum) % HASH_TAB_SIZE; } #ifdef GENCAT static char *program_name; static int message_compare P((UNIV, UNIV)); static void print_text P((char *, FILE *)); static void usage P((void)); #ifdef VARARGS static void fatal(); #else static void fatal P((char *,...)); #endif int main(argc, argv) int argc; char **argv; { FILE *fp; int i, j, nmessages; struct message **list; unsigned setnum; struct message *table[HASH_TAB_SIZE]; program_name = argv[0]; if (argc < 3) usage(); for (i = 0; i < HASH_TAB_SIZE; i++) table[i] = 0; for (i = 1; i < argc; i++) { errno = 0; fp = fopen(argv[i], "r"); if (!fp) { if (i > 1) fatal("can't open `%s': %s", argv[i], strerror(errno)); } else { current_lineno = 0; cat_errno = E_ZERO; if (read_catalog(fp, table) < 0) { assert(cat_errno != E_ZERO); assert(cat_errno < sizeof(cat_errlist)/sizeof(cat_errlist[0])); fatal("%s:%d: %s", argv[i], current_lineno, cat_errlist[cat_errno]); } fclose(fp); } } errno = 0; fp = fopen(argv[1], "w"); if (!fp) fatal("can't open `%s' for output: %s", argv[1], strerror(errno)); nmessages = 0; for (i = 0; i < HASH_TAB_SIZE; i++) { struct message *p; for (p = table[i]; p; p = p->next) nmessages++; } list = (struct message **)malloc(nmessages*sizeof(struct message *)); if (!list) fatal("out of memory"); j = 0; for (i = 0; i < HASH_TAB_SIZE; i++) { struct message *p; for (p = table[i]; p; p = p->next) list[j++] = p; } assert(j == nmessages); qsort((UNIV)list, nmessages, sizeof(struct message *), message_compare); setnum = NL_SETD; for (i = 0; i < nmessages; i++) { struct message *p = list[i]; if (p->setnum != setnum) { setnum = p->setnum; fprintf(fp, "$set %u\n", setnum); } fprintf(fp, "%u ", p->msgnum); print_text(p->text, fp); putc('\n', fp); } if (fclose(fp) == EOF) fatal("error closing `%s'", argv[1]); return 0; } static VOID usage() { fprintf(stderr, "usage: %s catfile msgfile...\n", program_name); exit(1); } static #ifdef VARARGS VOID fatal(va_alist) va_dcl #else /* not VARARGS */ VOID fatal(char *message,...) #endif /* not VARARGS */ { va_list ap; #ifdef VARARGS char *message; va_start(ap); message = va_arg(ap, char *); #else /* not VARARGS */ va_start(ap, message); #endif /* not VARARGS */ fprintf(stderr, "%s: ", program_name); vfprintf(stderr, message, ap); putc('\n', stderr); va_end(ap); exit(1); } static int message_compare(p1, p2) UNIV p1, UNIV p2; { struct message *m1 = *(struct message **)p1; struct message *m2 = *(struct message **)p2; if (m1->setnum < m2->setnum) return -1; if (m1->setnum > m2->setnum) return 1; if (m1->msgnum < m2->msgnum) return -1; if (m1->msgnum > m2->msgnum) return 1; return 0; } static VOID print_text(s, fp) char *s; FILE *fp; { for (; *s; s++) { if (*s == '\\') fputs("\\\\", fp); else if (ISASCII(*s) && isprint((UNCH)*s)) putc(*s, fp); else { switch (*s) { case '\n': fputs("\\n", fp); break; case '\b': fputs("\\b", fp); break; case '\f': fputs("\\f", fp); break; case '\t': fputs("\\t", fp); break; case '\v': fputs("\\v", fp); break; case '\r': fputs("\\r", fp); break; default: fprintf(fp, "\\%03o", (unsigned char)*s); break; } } } } #endif /* GENCAT */ #ifdef TEST int main(argc, argv) int argc; char **argv; { nl_catd catd; int msgnum, setnum; if (argc != 2) { fprintf(stderr, "usage: %s catalogue\n", argv[0]); exit(1); } catd = catopen(argv[1], 0); fprintf(stderr, "Enter set number, message number pairs:\n"); fflush(stderr); while (scanf("%d %d", &setnum, &msgnum) == 2) { char *msg = catgets(catd, setnum, msgnum, ""); fprintf(stderr, "Returned \"%s\"\n", msg); fflush(stderr); } return 0; } #endif /* TEST */ #endif /* not HAVE_CAT */ /* Local Variables: c-indent-level: 5 c-continued-statement-offset: 5 c-brace-offset: -5 c-argdecl-indent: 0 c-label-offset: -5 End: */