Add GNU LGPL headers to all .c .C and .h files
[oweals/cde.git] / cde / programs / dtdocbook / sgmls / msgcat.c
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /* $XConsortium: msgcat.c /main/3 1996/06/19 17:16:22 drk $ */
24 /* msgcat.c -
25    X/Open message catalogue functions and gencat utility.
26
27      Written by James Clark (jjc@jclark.com).
28 */
29
30 #include "config.h"
31
32 #ifndef HAVE_CAT
33
34 /* In this implementation the message catalogue format is the same as the
35 message text source file format (see pp 42-43 of the X/Open
36 Portability Guide, Issue 3, Volume 3.)  This means that you don't have
37 to use the gencat utility, but it is still useful for checking and
38 merging catalogues. */
39
40 /* Compile this with -DGENCAT to get the gencat utility. */
41
42 #include "std.h"
43 #include "msgcat.h"
44
45 #ifdef USE_PROTOTYPES
46 #define P(parms) parms
47 #else
48 #define P(parms) ()
49 #endif
50
51 /* Default message set. */
52 #define NL_SETD 1
53
54 #ifndef PATH_FILE_SEP
55 #define PATH_FILE_SEP ':'
56 #endif
57
58 #ifndef DEFAULT_NLSPATH
59 #define DEFAULT_NLSPATH ""
60 #endif
61
62 #ifndef DEFAULT_LANG
63 #define DEFAULT_LANG "default"
64 #endif
65
66 #define HASH_TAB_SIZE 251
67
68 struct message {
69      struct message *next;
70      unsigned msgnum;
71      unsigned setnum;
72      char *text;
73 };
74      
75 struct cat {
76      char *name;
77      int loaded;
78      int bad;
79      struct message *table[HASH_TAB_SIZE];
80 };
81
82 static char *read_buf = 0;
83 static unsigned read_buf_len = 0;
84
85 /* Errors that can be generated by read_catalog. */
86
87 enum cat_err {
88      E_ZERO,                    /* not an error */
89      E_BADARG,
90      E_NOMEM,
91      E_NOSUCHCOMMAND,
92      E_INPUT,
93      E_EOF,
94      E_BADSEP,
95      E_BADLINE
96 };
97
98 #ifdef GENCAT
99 /* These must match enum cat_err. */
100 static char *cat_errlist[] = {
101      "Error 0",
102      "Invalid argument to command",
103      "Out of memory",
104      "Unrecognized command",
105      "Input error",
106      "Unexpected end of file",
107      "Space or tab expected after message number",
108      "Invalid line",
109 };
110 #endif /* GENCAT */
111
112 #ifndef GENCAT
113 /* The value of NLSPATH. */
114 static char *nlspath = 0;
115 /* The value of LANG. */
116 static char *lang = 0;
117 #endif /* not GENCAT */
118
119 static int current_lineno = -1;
120 static enum cat_err cat_errno = E_ZERO;
121
122 #ifndef GENCAT
123 static void load_catalog P((struct cat *));
124 static FILE *find_catalog P((char *, char **));
125 #endif
126 static int read_catalog P((FILE *, struct message **));
127 static void delete_set P((struct message **, unsigned));
128 static void delete_message P((struct message **, unsigned, unsigned));
129 static int hash P((unsigned setnum, unsigned msgnum));
130 static char *parse_text P((FILE *, int));
131
132 #ifndef GENCAT
133
134 nl_catd catopen(name, oflag)
135 char *name;
136 int oflag;
137 {
138      struct cat *catp;
139      int i;
140
141      if (!name)
142           return 0;
143      
144      catp = (struct cat *)malloc(sizeof *catp);
145      if (!catp)
146           return 0;
147      for (i = 0; i < HASH_TAB_SIZE; i++)
148           catp->table[i] = 0;
149      catp->name = malloc(strlen(name) + 1);
150      catp->loaded = 0;
151      catp->bad = 0;
152      strcpy(catp->name, name);
153      return (nl_catd)catp;
154 }
155
156 int catclose(catd)
157 nl_catd catd;
158 {
159      int i;
160      struct cat *catp = (struct cat *)catd;
161
162      if (!catp)
163           return 0;
164
165      for (i = 0; i < HASH_TAB_SIZE; i++) {
166           struct message *p, *nextp;
167           for (p = catp->table[i]; p; p = nextp) {
168                nextp = p->next;
169                free(p->text);
170                free((char *)p);
171           }
172      }
173      if (catp->name)
174           free(catp->name);
175      free((char *)catp);
176      return 0;
177 }
178
179 char *catgets(catd, setnum, msgnum, dflt)
180 nl_catd catd;
181 int setnum, msgnum;
182 char *dflt;
183 {
184      struct message *p;
185      struct cat *catp;
186
187      /* setnum and msgnum are required to be >= 1. */
188      if (!catd || setnum <= 0 || msgnum <= 0)
189           return dflt;
190      catp = (struct cat *)catd;
191      if (!catp->loaded)
192           load_catalog(catp);
193      if (catp->bad)
194           return dflt;
195      for (p = catp->table[hash(setnum, msgnum)]; p; p = p->next)
196           if (p->msgnum == msgnum && p->setnum == setnum)
197                break;
198      if (!p)
199           return dflt;
200      return p->text;
201 }
202
203 static
204 VOID load_catalog(catp)
205 struct cat *catp;
206 {
207      FILE *fp;
208      char *path;
209
210      catp->loaded = 1;
211      fp = find_catalog(catp->name, &path);
212      if (!fp) {
213           catp->bad = 1;
214           return;
215      }
216      current_lineno = 0;
217      if (read_catalog(fp, catp->table) < 0)
218           catp->bad = 1;
219      fclose(fp);
220      if (read_buf) {
221           free(read_buf);
222           read_buf = 0;
223      }
224      read_buf_len = 0;
225      free(path);
226 }
227
228 static
229 FILE *find_catalog(name, pathp)
230 char *name;
231 char **pathp;
232 {
233      char *path;
234
235      if (!name)
236           return 0;
237      if (!nlspath) {
238           nlspath = getenv("NLSPATH");
239           if (!nlspath)
240                nlspath = DEFAULT_NLSPATH;
241      }
242      if (!lang) {
243           lang = getenv("LANG");
244           if (!lang)
245                lang = DEFAULT_LANG;
246      }
247      path = nlspath;
248      for (;;) {
249           char *p;
250           unsigned len = 0;
251
252           for (p = path; *p != '\0' && *p != PATH_FILE_SEP; p++) {
253                if (*p == '%') {
254                     if (p[1] == 'N') {
255                          p++;
256                          len += strlen(name);
257                     }
258                     else if (p[1] == 'L') {
259                          p++;
260                          len += strlen(lang);
261                     }
262                     else if (p[1] == '%') {
263                          p++;
264                          len++;
265                     }
266                     else
267                          len++;
268
269                }
270                else
271                     len++;
272           }
273           if (len > 0) {
274                char *s, *try;
275                FILE *fp;
276                s = try = malloc(len + 1);
277                if (!s)
278                     return 0;
279                for (p = path; *p != '\0' && *p != PATH_FILE_SEP; p++) {
280                     if (*p == '%') {
281                          if (p[1] == 'N') {
282                               p++;
283                               strcpy(s, name);
284                               s += strlen(name);
285                          }
286                          else if (p[1] == 'L') {
287                               p++;
288                               strcpy(s, lang);
289                               s += strlen(lang);
290                          }
291                          else if (p[1] == '%') {
292                               p++;
293                               *s++ = '%';
294                          }
295                          else
296                               *s++ = *p;
297                     }
298                     else
299                          *s++ = *p;
300                }
301                *s++ = '\0';
302                fp = fopen(try, "r");
303                if (fp) {
304                     *pathp = try;
305                     return fp;
306                }
307                free(try);
308           }
309           if (*p == '\0')
310                break;
311           path = ++p;
312      }
313      return 0;
314 }
315
316 #endif /* not GENCAT */
317
318 /* 0 success, -1 error */
319
320 static
321 int parse_message(c, fp, table, setnum, quote)
322 int c;
323 FILE *fp;
324 struct message **table;
325 unsigned setnum;
326 int quote;
327 {
328      unsigned msgnum;
329      struct message *msgp;
330      char *text;
331      int hc;
332
333      msgnum = c - '0';
334      for (;;) {
335           c = getc(fp);
336           if (!isdigit(c))
337                break;
338           msgnum = msgnum*10 + (c - '0');
339      }
340      if (c == '\n') {
341           delete_message(table, setnum, msgnum);
342           return 0;
343      }
344      if (c != ' ' && c != '\t') {
345           cat_errno = E_BADSEP;
346           return -1;
347      }
348      text = parse_text(fp, quote);
349      if (!text)
350           return -1;
351      hc = hash(setnum, msgnum);
352      for (msgp = table[hc]; msgp; msgp = msgp->next)
353           if (msgp->setnum == setnum && msgp->msgnum == msgnum)
354                break;
355      if (msgp)
356           free(msgp->text);
357      else {
358           msgp = (struct message *)malloc(sizeof *msgp);
359           if (!msgp) {
360                cat_errno = E_NOMEM;
361                return -1;
362           }
363           msgp->next = table[hc];
364           table[hc] = msgp;
365           msgp->msgnum = msgnum;
366           msgp->setnum = setnum;
367      }
368      msgp->text = text;
369      return 0;
370 }
371
372 static
373 char *parse_text(fp, quote)
374 FILE *fp;
375 int quote;
376 {
377      unsigned i = 0;
378      char *p;
379      int c;
380      int quoted;
381
382      c = getc(fp);
383      if (c == quote) {
384           quoted = 1;
385           c = getc(fp);
386      }
387      else
388           quoted = 0;
389      for (;; c = getc(fp)) {
390           if (c == EOF) {
391                if (ferror(fp)) {
392                     cat_errno = E_INPUT;
393                     return 0;
394                }
395                break;
396           }
397           if (c == '\n')
398                break;
399           /* XXX
400
401              Can quotes be used in quoted message text if protected by \ ?
402
403              Is it illegal to omit the closing quote if there's an opening
404              quote?
405
406              Is it illegal to have anything after a closing quote?
407
408           */
409
410           if (quoted && c == quote) {
411                /* Skip the rest of the line. */
412                while ((c = getc(fp)) != '\n')
413                     if (c == EOF) {
414                          if (ferror(fp)) {
415                               cat_errno = E_INPUT;
416                               return 0;
417                          }
418                          break;
419                     }
420                break;
421           }
422           if (c == '\\') {
423                int d;
424
425                c = getc(fp);
426                if (c == EOF)
427                     break;
428                switch (c) {
429                case '\n':
430                     current_lineno++;
431                     continue;
432                case 'n':
433                     c = '\n';
434                     break;
435                case 'b':
436                     c = '\b';
437                     break;
438                case 'f':
439                     c = '\f';
440                     break;
441                case 't':
442                     c = '\t';
443                     break;
444                case 'v':
445                     c = '\v';
446                     break;
447                case 'r':
448                     c = '\r';
449                     break;
450                case '\\':
451                     c = '\\';
452                     break;
453                case '0':
454                case '1':
455                case '2':
456                case '3':
457                case '4':
458                case '5':
459                case '6':
460                case '7':
461                     c -= '0';
462                     d = getc(fp);
463                     if (d >= '0' && d <= '7') {
464                          c = c*8 + d - '0';
465                          d = getc(fp);
466                          if (d >= '0' && d <= '7')
467                               c = c*8 + d - '0';
468                          else if (d != EOF)
469                               ungetc(d,fp);
470                     }
471                     else if (d != EOF)
472                          ungetc(d, fp);
473                     if (c == '\0')
474                          continue; /* XXX */
475                     break;
476                default:
477                     /* Ignore the quote. */
478                     break;
479                }
480           }
481           if (i >= read_buf_len) {
482                if (!read_buf)
483                     read_buf = malloc(read_buf_len = 40);
484                else
485                     read_buf = realloc(read_buf, read_buf_len *= 2);
486                if (!read_buf) {
487                     cat_errno = E_NOMEM;
488                     return 0;
489                }
490           }
491           read_buf[i++] = c;
492      }
493      p = malloc(i + 1);
494      if (!p) {
495           cat_errno = E_NOMEM;
496           return 0;
497      }
498      memcpy(p, read_buf, i);
499      p[i] = '\0';
500      return p;
501 }
502           
503 /* 0 success, -1 error */
504
505 static
506 int parse_command(fp, table, setnump, quotep)
507 FILE *fp;
508 struct message **table;
509 unsigned *setnump;
510 int *quotep;
511 {
512      char buf[128];
513      if (fgets(buf, 128, fp) == NULL) {
514           cat_errno = ferror(fp) ? E_INPUT : E_EOF;
515           return -1;
516      }
517      if (buf[0] == ' ' || buf[0] == '\t' || buf[0] == '\n')
518           /* a comment */;
519      else if (strncmp(buf, "set", 3) == 0) {
520           if (sscanf(buf + 3, "%u", setnump) != 1) {
521                cat_errno = E_BADARG;
522                return -1;
523           }
524
525      }
526      else if (strncmp(buf, "delset", 6) == 0) {
527           unsigned num;
528           if (sscanf(buf + 6, "%u", &num) != 1) {
529                cat_errno = E_BADARG;
530                return -1;
531           }
532           delete_set(table, num);
533           *setnump = NL_SETD;
534      }
535      else if (strncmp(buf, "quote", 5) == 0) {
536           char *p = buf + 5;
537           while (*p == ' ' || *p == '\t')
538                p++;
539           /* XXX should \ be allowed as the quote character? */
540           if (*p == '\0' || *p == '\n')
541                *quotep = -1;
542           else
543                *quotep = *p;
544      }
545      else {
546           cat_errno = E_NOSUCHCOMMAND;
547           return -1;
548      }
549      if (strchr(buf, '\n') == 0) {
550           int c;
551           while ((c = getc(fp)) != '\n' && c != EOF)
552                ;
553      }
554      return 0;
555 }
556
557
558 static
559 VOID delete_set(table, setnum)
560 struct message **table;
561 unsigned setnum;
562 {
563      int i;
564
565      for (i = 0; i < HASH_TAB_SIZE; i++) {
566           struct message *p, *nextp;
567           for (p = table[i], table[i] = 0; p; p = nextp) {
568                nextp = p->next;
569                if (p->setnum == setnum)
570                     free((char *)p);
571                else {
572                     p->next = table[i];
573                     table[i] = p;
574                }
575           }
576      }
577 }
578
579 static
580 VOID delete_message(table, setnum, msgnum)
581 struct message **table;
582 unsigned setnum, msgnum;
583 {
584      struct message **pp;
585      
586      for (pp = &table[hash(setnum, msgnum)]; *pp; pp = &(*pp)->next)
587           if ((*pp)->setnum == setnum && (*pp)->msgnum == msgnum) {
588                struct message *p = *pp;
589                *pp = p->next;
590                free(p->text);
591                free((char *)p);
592                break;
593           }
594 }
595
596 /* 0 success, -1 error. On error cat_errno is set to the error number. */
597
598 static
599 int read_catalog(fp, table)
600 FILE *fp;
601 struct message **table;
602 {
603      int c;
604      unsigned setnum = NL_SETD;
605      int quote_char = -1;
606
607      for (;;) {
608           /* start of line */
609           c = getc(fp);
610           if (c == EOF)
611                break;
612           ++current_lineno;
613           if (isdigit(c)) {
614                if (parse_message(c, fp, table, setnum, quote_char) < 0)
615                     return -1;
616           }
617           else if (c == '$') {
618                if (parse_command(fp, table, &setnum, &quote_char) < 0)
619                     return -1;
620           }
621           else if (c != '\n') {
622                while ((c = getc(fp)) != '\n' && c != EOF)
623                     if (c != ' ' && c != '\t') {
624                          cat_errno = E_BADLINE;
625                          return -1;
626                     }
627                if (c == EOF)
628                     break;
629           }
630      }
631      return 0;
632 }
633
634 static
635 int hash(setnum, msgnum)
636 unsigned setnum, msgnum;
637 {
638      return ((setnum << 8) + msgnum) % HASH_TAB_SIZE;
639 }
640
641 #ifdef GENCAT
642
643 static char *program_name;
644
645 static int message_compare P((UNIV, UNIV));
646 static void print_text P((char *, FILE *));
647 static void usage P((void));
648
649 #ifdef VARARGS
650 static void fatal();
651 #else
652 static void fatal P((char *,...));
653 #endif
654
655 int main(argc, argv)
656 int argc;
657 char **argv;
658 {
659      FILE *fp;
660      int i, j, nmessages;
661      struct message **list;
662      unsigned setnum;
663      struct message *table[HASH_TAB_SIZE];
664     
665      program_name = argv[0];
666      
667      if (argc < 3)
668           usage();
669
670      for (i = 0; i < HASH_TAB_SIZE; i++)
671           table[i] = 0;
672      for (i = 1; i < argc; i++) {
673           errno = 0;
674           fp = fopen(argv[i], "r");
675           if (!fp) {
676                if (i > 1)
677                     fatal("can't open `%s': %s", argv[i], strerror(errno));
678           }
679           else {
680                current_lineno = 0;
681                cat_errno = E_ZERO;
682                if (read_catalog(fp, table) < 0) {
683                     assert(cat_errno != E_ZERO);
684                     assert(cat_errno
685                            < sizeof(cat_errlist)/sizeof(cat_errlist[0]));
686                     fatal("%s:%d: %s", argv[i], current_lineno,
687                           cat_errlist[cat_errno]);
688                }
689                fclose(fp);
690           }
691      }
692      
693      errno = 0;
694      fp = fopen(argv[1], "w");
695      if (!fp)
696           fatal("can't open `%s' for output: %s", argv[1], strerror(errno));
697      nmessages = 0;
698      for (i = 0; i < HASH_TAB_SIZE; i++) {
699           struct message *p;
700           for (p = table[i]; p; p = p->next)
701                nmessages++;
702      }
703      list = (struct message **)malloc(nmessages*sizeof(struct message *));
704      if (!list)
705           fatal("out of memory");
706      j = 0;
707      for (i = 0; i < HASH_TAB_SIZE; i++) {
708           struct message *p;
709           for (p = table[i]; p; p = p->next)
710                list[j++] = p;
711      }
712      assert(j == nmessages);
713      
714      qsort((UNIV)list, nmessages, sizeof(struct message *), message_compare);
715
716      setnum = NL_SETD;
717      for (i = 0; i < nmessages; i++) {
718           struct message *p = list[i];
719           if (p->setnum != setnum) {
720                setnum = p->setnum;
721                fprintf(fp, "$set %u\n", setnum);
722           }
723           fprintf(fp, "%u ", p->msgnum);
724           print_text(p->text, fp);
725           putc('\n', fp);
726      }
727      if (fclose(fp) == EOF)
728           fatal("error closing `%s'", argv[1]);
729      return 0;
730 }
731
732 static
733 VOID usage()
734 {
735      fprintf(stderr, "usage: %s catfile msgfile...\n", program_name);
736      exit(1);
737 }
738
739 static
740 #ifdef VARARGS
741 VOID fatal(va_alist) va_dcl
742 #else /* not VARARGS */
743 VOID fatal(char *message,...)
744 #endif /* not VARARGS */
745 {
746      va_list ap;
747
748 #ifdef VARARGS
749      char *message;
750      va_start(ap);
751      message = va_arg(ap, char *);
752 #else /* not VARARGS */
753      va_start(ap, message);
754 #endif /* not VARARGS */ 
755      
756      fprintf(stderr, "%s: ", program_name);
757      vfprintf(stderr, message, ap);
758      putc('\n', stderr);
759      va_end(ap);
760      exit(1);
761 }
762
763 static
764 int message_compare(p1, p2)
765 UNIV p1, UNIV p2;
766 {
767      struct message *m1 = *(struct message **)p1;
768      struct message *m2 = *(struct message **)p2;
769
770      if (m1->setnum < m2->setnum)
771           return -1;
772      if (m1->setnum > m2->setnum)
773           return 1;
774      if (m1->msgnum < m2->msgnum)
775           return -1;
776      if (m1->msgnum > m2->msgnum)
777           return 1;
778      return 0;
779 }
780
781 static
782 VOID print_text(s, fp)
783 char *s;
784 FILE *fp;
785 {
786      for (; *s; s++) {
787           if (*s == '\\')
788                fputs("\\\\", fp);
789           else if (ISASCII(*s) && isprint((UNCH)*s))
790                putc(*s, fp);
791           else {
792                switch (*s) {
793                case '\n':
794                     fputs("\\n", fp);
795                     break;
796                case '\b':
797                     fputs("\\b", fp);
798                     break;
799                case '\f':
800                     fputs("\\f", fp);
801                     break;
802                case '\t':
803                     fputs("\\t", fp);
804                     break;
805                case '\v':
806                     fputs("\\v", fp);
807                     break;
808                case '\r':
809                     fputs("\\r", fp);
810                     break;
811                default:
812                     fprintf(fp, "\\%03o", (unsigned char)*s);
813                     break;
814                }
815           }
816      }
817 }
818
819 #endif /* GENCAT */
820
821 #ifdef TEST
822
823 int main(argc, argv)
824 int argc;
825 char **argv;
826 {
827      nl_catd catd;
828      int msgnum, setnum;
829      
830      if (argc != 2) {
831           fprintf(stderr, "usage: %s catalogue\n", argv[0]);
832           exit(1);
833      }
834      catd = catopen(argv[1], 0);
835      fprintf(stderr, "Enter set number, message number pairs:\n");
836      fflush(stderr);
837      while (scanf("%d %d", &setnum, &msgnum) == 2) {
838           char *msg = catgets(catd, setnum, msgnum, "<default>");
839           fprintf(stderr, "Returned \"%s\"\n", msg);
840           fflush(stderr);
841      }
842      return 0;
843 }
844
845 #endif /* TEST */
846
847 #endif /* not HAVE_CAT */
848 /*
849 Local Variables:
850 c-indent-level: 5
851 c-continued-statement-offset: 5
852 c-brace-offset: -5
853 c-argdecl-indent: 0
854 c-label-offset: -5
855 End:
856 */