home *** CD-ROM | disk | FTP | other *** search
/ Gold Fish 3 / goldfish_volume_3.bin / files / dev / lang / sgmls / src / msgcat.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-07-10  |  16.0 KB  |  841 lines

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