Tue Apr 9 14:37:31 1996 Ulrich Drepper <drepper@cygnus.com>
[kopensolaris-gnu/glibc.git] / catgets / gencat.c
1 /* Copyright (C) 1996 Free Software Foundation, Inc.
2 This file is part of the GNU C Library.
3 Contributed by Ulrich Drepper, <drepper@gnu.ai.mit.edu>.
4
5 The GNU C Library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public License as
7 published by the Free Software Foundation; either version 2 of the
8 License, or (at your option) any later version.
9
10 The GNU C Library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public
16 License along with the GNU C Library; see the file COPYING.LIB.  If
17 not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18 Boston, MA 02111-1307, USA.  */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23
24 #include <ctype.h>
25 #include <endian.h>
26 #include <errno.h>
27 #include <error.h>
28 #include <fcntl.h>
29 #include <getopt.h>
30 #include <libintl.h>
31 #include <limits.h>
32 #include <nl_types.h>
33 #include <obstack.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <unistd.h>
38
39 #include "version.h"
40
41 #include "catgetsinfo.h"
42
43
44 #define SWAPU32(w) \
45   (((w) << 24) | (((w) & 0xff00) << 8) | (((w) >> 8) & 0xff00) | ((w) >> 24))
46
47 struct message_list
48 {
49   int number;
50   const char *message;
51
52   const char *fname;
53   size_t line;
54   const char *symbol;
55
56   struct message_list *next;
57 };
58
59
60 struct set_list
61 {
62   int number;
63   int deleted;
64   struct message_list *messages;
65   int last_message;
66
67   const char *fname;
68   size_t line;
69   const char *symbol;
70
71   struct set_list *next;
72 };
73
74
75 struct catalog
76 {
77   struct set_list *all_sets;
78   struct set_list *current_set;
79   size_t total_messages;
80   char quote_char;
81   int last_set;
82
83   struct obstack mem_pool;
84 };
85
86
87 /* If non-zero force creation of new file, not using existing one.  */
88 static int force_new;
89
90 /* Long options.  */
91 static const struct option long_options[] =
92 {
93   { "header", required_argument, NULL, 'H' },
94   { "help", no_argument, NULL, 'h' },
95   { "new", no_argument, &force_new, 1 },
96   { "output", required_argument, NULL, 'o' },
97   { "version", no_argument, NULL, 'V' },
98   { NULL, 0, NULL, 0 }
99 };
100
101 /* Wrapper functions with error checking for standard functions.  */
102 extern void *xmalloc (size_t n);
103
104 /* Prototypes for local functions.  */
105 static void usage (int status) __attribute__ ((noreturn));
106 static void error_print (void);
107 static struct catalog *read_input_file (struct catalog *current,
108                                         const char *fname);
109 static void write_out (struct catalog *result, const char *output_name,
110                        const char *header_name);
111 static struct set_list *find_set (struct catalog *current, int number);
112 static void normalize_line (const char *fname, size_t line, char *string,
113                             char quote_char);
114 static void read_old (struct catalog *catalog, const char *file_name);
115
116
117 int
118 main (int argc, char *argv[])
119 {
120   struct catalog *result;
121   const char *output_name;
122   const char *header_name;
123   int do_help;
124   int do_version;
125   int opt;
126
127   /* Set program name for messages.  */
128   error_print_progname = error_print;
129
130   /* Set locale via LC_ALL.  */
131   setlocale (LC_ALL, "");
132
133   /* Set the text message domain.  */
134   textdomain (PACKAGE);
135
136   /* Initialize local variables.  */
137   do_help = 0;
138   do_version = 0;
139   output_name = NULL;
140   header_name = NULL;
141   result = NULL;
142
143   while ((opt = getopt_long (argc, argv, "hH:o:V", long_options, NULL)) != EOF)
144     switch (opt)
145       {
146       case '\0':        /* Long option.  */
147         break;
148       case 'h':
149         do_help = 1;
150         break;
151       case 'H':
152         header_name = optarg;
153         break;
154       case 'o':
155         output_name = optarg;
156         break;
157       case 'V':
158         do_version = 1;
159         break;
160       default:
161         usage (EXIT_FAILURE);
162       }
163
164   /* Version information is requested.  */
165   if (do_version)
166     {
167       fprintf (stderr, "%s - GNU %s %s\n", program_invocation_name,
168                PACKAGE, VERSION);
169       exit (EXIT_SUCCESS);
170     }
171
172   /* Help is requested.  */
173   if (do_help)
174     usage (EXIT_SUCCESS);
175
176   /* Determine output file.  */
177   if (output_name == NULL)
178     output_name = optind < argc ? argv[optind++] : "-";
179
180   /* Process all input files.  */
181   setlocale (LC_CTYPE, "C");
182   if (optind < argc)
183     do
184       result = read_input_file (result, argv[optind]);
185     while (++optind < argc);
186   else
187     result = read_input_file (NULL, "-");
188
189   /* Write out the result.  */
190   if (result != NULL)
191     write_out (result, output_name, header_name);
192
193   exit (EXIT_SUCCESS);
194 }
195
196
197 static void
198 usage (int status)
199 {
200   if (status != EXIT_SUCCESS)
201     fprintf (stderr, gettext ("Try `%s --help' for more information.\n"),
202              program_invocation_name);
203   else
204     printf(gettext ("\
205 Usage: %s [OPTION]... -o OUTPUT-FILE [INPUT-FILE]...\n\
206        %s [OPTION]... [OUTPUT-FILE [INPUT-FILE]...]\n\
207 Mandatory arguments to long options are mandatory for short options too.\n\
208   -H, --header        create C header file containing symbol definitions\n\
209   -h, --help          display this help and exit\n\
210       --new           do not use existing catalog, force new output file\n\
211   -o, --output=NAME   write output to file NAME\n\
212   -V, --version       output version information and exit\n\
213 If INPUT-FILE is -, input is read from standard input.  If OUTPUT-FILE\n\
214 is -, output is written to standard output.\n"),
215            program_invocation_name, program_invocation_name);
216
217   exit (status);
218 }
219
220
221 /* The address of this function will be assigned to the hook in the
222    error functions.  */
223 static void
224 error_print ()
225 {
226   /* We don't want the program name to be printed in messages.  Emacs'
227      compile.el does not like this.  */
228 }
229
230
231 static struct catalog *
232 read_input_file (struct catalog *current, const char *fname)
233 {
234   FILE *fp;
235   char *buf;
236   size_t len;
237   size_t line_number;
238
239   if (strcmp (fname, "-") == 0 || strcmp (fname, "/dev/stdin") == 0)
240     {
241       fp = stdin;
242       fname = gettext ("*standard input*");
243     }
244   else
245     fp = fopen (fname, "r");
246   if (fp == NULL)
247     {
248       error (0, errno, gettext ("cannot open input file `%s'"), fname);
249       return current;
250     }
251
252   /* If we haven't seen anything yet, allocate result structure.  */
253   if (current == NULL)
254     {
255       current = (struct catalog *) xmalloc (sizeof (*current));
256
257       current->all_sets = NULL;
258       current->total_messages = 0;
259       current->last_set = 0;
260       current->current_set = find_set (current, NL_SETD);
261
262 #define obstack_chunk_alloc xmalloc
263 #define obstack_chunk_free free
264       obstack_init (&current->mem_pool);
265     }
266
267   buf = NULL;
268   len = 0;
269   line_number = 0;
270   while (!feof (fp))
271     {
272       int continued;
273       int used;
274       size_t start_line = line_number + 1;
275       char *this_line;
276
277       do
278         {
279           int act_len;
280
281           act_len = getline (&buf, &len, fp);
282           if (act_len <= 0)
283             break;
284           ++line_number;
285
286           /* It the line continued?  */
287           if (buf[act_len - 1] == '\n')
288             {
289               --act_len;
290               continued = buf[act_len - 1] == '\\';
291               if (continued)
292                 --act_len;
293             }
294           else
295             continued = 0;
296
297           /* Append to currently selected line.  */
298           obstack_grow (&current->mem_pool, buf, act_len);
299         }
300       while (continued);
301
302       obstack_1grow (&current->mem_pool, '\0');
303       this_line = (char *) obstack_finish (&current->mem_pool);
304
305       used = 0;
306       if (this_line[0] == '$')
307         {
308           if (isspace (this_line[1]))
309             /* This is a comment line.  Do nothing.  */;
310           else if (strncmp (&this_line[1], "set", 3) == 0)
311             {
312               int cnt = sizeof ("cnt");
313               size_t set_number;
314               const char *symbol = NULL;
315               while (isspace (this_line[cnt]))
316                 ++cnt;
317
318               if (isdigit (this_line[cnt]))
319                 {
320                   set_number = atol (&this_line[cnt]);
321
322                   /* If the given number for the character set is
323                      higher than any we used for symbolic set names
324                      avoid clashing by using only higher numbers for
325                      the following symbolic definitions.  */
326                   if (set_number > current->last_set)
327                     current->last_set = set_number;
328                 }
329               else
330                 {
331                   /* See whether it is a reasonable identifier.  */
332                   int start = cnt;
333                   while (isalnum (this_line[cnt]) || this_line[cnt] == '_')
334                     ++cnt;
335
336                   if (cnt == start)
337                     {
338                       /* No correct character found.  */
339                       error_at_line (0, 0, fname, start_line,
340                                      gettext ("illegal set number"));
341                       set_number = 0;
342                     }
343                   else
344                     {
345                       /* We have found seomthing which looks like a
346                          correct identifier.  */
347                       struct set_list *runp;
348
349                       this_line[cnt] = '\0';
350                       used = 1;
351                       symbol = &this_line[start];
352
353                       /* Test whether the identifier was already used.  */
354                       runp = current->all_sets;
355                       while (runp != 0)
356                         if (runp->symbol != NULL
357                             && strcmp (runp->symbol, symbol) == 0)
358                           break;
359                         else
360                           runp = runp->next;
361
362                       if (runp != NULL)
363                         {
364                           /* We cannot allow duplicate identifiers for
365                              message sets.  */
366                           error_at_line (0, 0, fname, start_line,
367                                          gettext ("duplicate set definition"));
368                           error_at_line (0, 0, runp->fname, runp->line,
369                                          gettext ("\
370 this is the first definition"));
371                           set_number = 0;
372                         }
373                       else
374                         /* Allocate next free message set for identifier.  */
375                         set_number = ++current->last_set;
376                     }
377                 }
378
379               if (set_number != 0)
380                 {
381                   /* We found a legal set number.  */
382                   current->current_set = find_set (current, set_number);
383                   if (symbol != NULL)
384                       used = 1;
385                   current->current_set->symbol = symbol;
386                   current->current_set->fname = fname;
387                   current->current_set->line = start_line;
388                 }
389             }
390           else if (strncmp (&this_line[1], "delset", 6) == 0)
391             {
392               int cnt = sizeof ("delset");
393               size_t set_number;
394               while (isspace (this_line[cnt]))
395                 ++cnt;
396
397               if (isdigit (this_line[cnt]))
398                 {
399                   size_t set_number = atol (&this_line[cnt]);
400                   struct set_list *set;
401
402                   /* Mark the message set with the given number as
403                      deleted.  */
404                   set = find_set (current, set_number);
405                   set->deleted = 1;
406                 }
407               else
408                 {
409                   /* See whether it is a reasonable identifier.  */
410                   int start = cnt;
411                   while (isalnum (this_line[cnt]) || this_line[cnt] == '_')
412                     ++cnt;
413
414                   if (cnt == start)
415                     {
416                       error_at_line (0, 0, fname, start_line,
417                                      gettext ("illegal set number"));
418                       set_number = 0;
419                     }
420                   else
421                     {
422                       const char *symbol;
423                       struct set_list *runp;
424
425                       this_line[cnt] = '\0';
426                       used = 1;
427                       symbol = &this_line[start];
428
429                       /* We have a symbolic set name.  This name must
430                          appear somewhere else in the catalogs read so
431                          far.  */
432                       set_number = 0;
433                       for (runp = current->all_sets; runp != NULL;
434                            runp = runp->next)
435                         {
436                           if (strcmp (runp->symbol, symbol) == 0)
437                             {
438                               runp->deleted = 1;
439                               break;
440                             }
441                         }
442                       if (runp == NULL)
443                         /* Name does not exist before.  */
444                         error_at_line (0, 0, fname, start_line,
445                                        gettext ("unknown set `%s'"), symbol);
446                     }
447                 }
448             }
449           else if (strncmp (&this_line[1], "quote", 5) == 0)
450             {
451               int cnt = sizeof ("quote");
452               while (isspace (this_line[cnt]))
453                 ++cnt;
454               /* Yes, the quote char can be '\0'; this means no quote
455                  char.  */
456               current->quote_char = this_line[cnt];
457             }
458           else
459             {
460               int cnt;
461               cnt = 2;
462               while (this_line[cnt] != '\0' && !isspace (this_line[cnt]))
463                 ++cnt;
464               this_line[cnt] = '\0';
465               error_at_line (0, 0, fname, start_line,
466                              gettext ("unknown directive `%s': line ignored"),
467                              &this_line[1]);
468             }
469         }
470       else if (isalnum (this_line[0]) || this_line[0] == '_')
471         {
472           const char *ident = this_line;
473           int message_number;
474
475           do
476             ++this_line;
477           while (this_line[0] != '\0' && !isspace (this_line[0]));;
478           this_line[0] = '\0';  /* Terminate the identifier.  */
479
480           do
481             ++this_line;
482           while (isspace (this_line[0]));
483           /* Now we found the beginning of the message itself.  */
484
485           if (isdigit (ident[0]))
486             {
487               struct message_list *runp;
488
489               message_number = atoi (ident);
490
491               /* Find location to insert the new message.  */
492               runp = current->current_set->messages;
493               while (runp != NULL)
494                 if (runp->number == message_number)
495                   break;
496                 else
497                   runp = runp->next;
498               if (runp != NULL)
499                 {
500                   /* Oh, oh.  There is already a message with this
501                      number is the message set.  */
502                   error_at_line (0, 0, fname, start_line,
503                                  gettext ("duplicated message number"));
504                   error_at_line (0, 0, runp->fname, runp->line,
505                                  gettext ("this is the first definition"));
506                   message_number = 0;
507                 }
508               ident = NULL;     /* We don't have a symbol.  */
509
510               if (message_number != 0
511                   && message_number > current->current_set->last_message)
512                 current->current_set->last_message = message_number;
513             }
514           else if (ident[0] != '\0')
515             {
516               struct message_list *runp;
517               runp = current->current_set->messages;
518
519               /* Test whether the symbolic name was not used for
520                  another message in this message set.  */
521               while (runp != NULL)
522                 if (runp->symbol != NULL && strcmp (ident, runp->symbol) == 0)
523                   break;
524                 else
525                   runp = runp->next;
526               if (runp != NULL)
527                 {
528                   /* The name is already used.  */
529                   error_at_line (0, 0, fname, start_line,
530                                  gettext ("duplicated message identifier"));
531                   error_at_line (0, 0, runp->fname, runp->line,
532                                  gettext ("this is the first definition"));
533                   message_number = 0;
534                 }
535               else
536                 /* Give the message the next unused number.  */
537                 message_number = ++current->current_set->last_message;
538             }
539           else
540             message_number = 0;
541
542           if (message_number != 0)
543             {
544               struct message_list *newp;
545
546               used = 1; /* Yes, we use the line.  */
547
548               /* Strip quote characters, change escape sequences into
549                  correct characters etc.  */
550               normalize_line (fname, start_line, this_line,
551                               current->quote_char);
552
553               newp = (struct message_list *) xmalloc (sizeof (*newp));
554               newp->number = message_number;
555               newp->message = this_line;
556               /* Remember symbolic name; is NULL if no is given.  */
557               newp->symbol = ident;
558               /* Remember where we found the character.  */
559               newp->fname = fname;
560               newp->line = start_line;
561
562               /* Find place to insert to message.  We keep them in a
563                  sorted single linked list.  */
564               if (current->current_set->messages == NULL
565                   || current->current_set->messages->number > message_number)
566                 {
567                   newp->next = current->current_set->messages;
568                   current->current_set->messages = newp;
569                 }
570               else
571                 {
572                   struct message_list *runp;
573                   runp = current->current_set->messages;
574                   while (runp->next != NULL)
575                     if (runp->next->number > message_number)
576                       break;
577                     else
578                       runp = runp->next;
579                   newp->next = runp->next;
580                   runp->next = newp;
581                 }
582             }
583           ++current->total_messages;
584         }
585       else
586         {
587           size_t cnt;
588
589           cnt = 0;
590           /* See whether we have any non-white space character in this
591              line.  */
592           while (this_line[cnt] != '\0' && isspace (this_line[cnt]))
593             ++cnt;
594
595           if (this_line[cnt] != '\0')
596             /* Yes, some unknown characters found.  */
597             error_at_line (0, 0, fname, start_line,
598                            gettext ("malformed line ignored"));
599         }
600
601       /* We can save the memory for the line if it was not used.  */
602       if (!used)
603         obstack_free (&current->mem_pool, this_line);
604     }
605
606   if (fp != stdin)
607     fclose (fp);
608   return current;
609 }
610
611
612 static void
613 write_out (struct catalog *catalog, const char *output_name,
614            const char *header_name)
615 {
616   /* Computing the "optimal" size.  */
617   struct set_list *set_run;
618   size_t best_total, best_size, best_depth;
619   size_t act_size, act_depth;
620   struct catalog_obj obj;
621   struct obstack string_pool;
622   const char *strings;
623   size_t strings_size;
624   u_int32_t *array1, *array2;
625   size_t cnt;
626   int fd;
627
628   /* If not otherwise told try to read file with existing
629      translations.  */
630   if (!force_new)
631     read_old (catalog, output_name);
632
633   /* Initialize best_size with a very high value.  */
634   best_total = best_size = best_depth = UINT_MAX;
635
636   /* We need some start size for testing.  Let's start with
637      TOTAL_MESSAGES / 5, which theoretically provides a mean depth of
638      5.  */
639   act_size = 1 + catalog->total_messages / 5;
640
641   /* We determine the size of a hash table here.  Because the message
642      numbers can be chosen arbitrary by the programmer we cannot use
643      the simple method of accessing the array using the message
644      number.  The algorithm is based on the trivial hash function
645      NUMBER % TABLE_SIZE, where collisions are stored in a second
646      dimension up to TABLE_DEPTH.  We here compute TABLE_SIZE so that
647      the needed space (= TABLE_SIZE * TABLE_DEPTH) is minimal.  */
648   while (act_size <= best_total)
649     {
650       size_t deep[act_size];
651
652       act_depth = 1;
653       memset (deep, '\0', act_size * sizeof (size_t));
654       set_run = catalog->all_sets;
655       while (set_run != NULL)
656         {
657           struct message_list *message_run;
658
659           message_run = set_run->messages;
660           while (message_run != NULL)
661             {
662               size_t idx = (message_run->number * set_run->number) % act_size;
663
664               ++deep[idx];
665               if (deep[idx] > act_depth)
666                 {
667                   act_depth = deep[idx];
668                   if (act_depth * act_size > best_total)
669                     break;
670                 }
671               message_run = message_run->next;
672             }
673
674           if (act_depth * act_size <= best_total)
675             {
676               /* We have found a better solution.  */
677               best_total = act_depth * act_size;
678               best_size = act_size;
679               best_depth = act_depth;
680             }
681           set_run = set_run->next;
682         }
683
684       ++act_size;
685     }
686
687   /* let's be prepared for an empty message file.  */
688   if (best_size == UINT_MAX)
689     {
690       best_size = 1;
691       best_depth = 1;
692     }
693
694   /* OK, now we have the size we will use.  Fill in the header, build
695      the table and the second one with swapped byte order.  */
696   obj.magic = CATGETS_MAGIC;
697   obj.plane_size = best_size;
698   obj.plane_depth = best_depth;
699
700   /* Allocate room for all needed arrays.  */
701   array1 =
702     (u_int32_t *) alloca (best_size * best_depth * sizeof (u_int32_t) * 3);
703   memset (array1, '\0', best_size * best_depth * sizeof (u_int32_t) * 3);
704   array2
705     = (u_int32_t *) alloca (best_size * best_depth * sizeof (u_int32_t) * 3);
706   obstack_init (&string_pool);
707
708   set_run = catalog->all_sets;
709   while (set_run != NULL)
710     {
711       struct message_list *message_run;
712
713       message_run = set_run->messages;
714       while (message_run != NULL)
715         {
716           size_t idx = (((message_run->number * set_run->number) % best_size)
717                         * 3);
718           /* Determine collision depth.  */
719           while (array1[idx] != 0)
720             idx += best_size * 3;
721
722           /* Store set number, message number and pointer into string
723              space, relative to the first string.  */
724           array1[idx + 0] = set_run->number;
725           array1[idx + 1] = message_run->number;
726           array1[idx + 2] = obstack_object_size (&string_pool);
727
728           /* Add current string to the continuous space containing all
729              strings.  */
730           obstack_grow0 (&string_pool, message_run->message,
731                          strlen (message_run->message));
732
733           message_run = message_run->next;
734         }
735
736       set_run = set_run->next;
737     }
738   strings_size = obstack_object_size (&string_pool);
739   strings = obstack_finish (&string_pool);
740
741   /* Compute ARRAY2 by changing the byte order.  */
742   for (cnt = 0; cnt < best_size * best_depth * 3; ++cnt)
743     array2[cnt] = SWAPU32 (array1[cnt]);
744
745   /* Now we can write out the whole data.  */
746   if (strcmp (output_name, "-") == 0
747       || strcmp (output_name, "/dev/stdout") == 0)
748     fd = STDOUT_FILENO;
749   else
750     {
751       fd = creat (output_name, 0666);
752       if (fd < 0)
753         error (EXIT_FAILURE, errno, gettext ("cannot open output file `%s'"),
754                output_name);
755     }
756
757   /* Write out header.  */
758   write (fd, &obj, sizeof (obj));
759
760   /* We always write out the little endian version of the index
761      arrays.  */
762 #if __BYTE_ORDER == __LITTLE_ENDIAN
763   write (fd, array1, best_size * best_depth * sizeof (u_int32_t) * 3);
764   write (fd, array2, best_size * best_depth * sizeof (u_int32_t) * 3);
765 #elif __BYTE_ORDER == __BIG_ENDIAN
766   write (fd, array2, best_size * best_depth * sizeof (u_int32_t) * 3);
767   write (fd, array1, best_size * best_depth * sizeof (u_int32_t) * 3);
768 #else
769 # error Cannot handle __BYTE_ORDER byte order
770 #endif
771
772   /* Finally write the strings.  */
773   write (fd, strings, strings_size);
774
775   if (fd != STDOUT_FILENO)
776     close (fd);
777
778   /* If requested now write out the header file.  */
779   if (header_name != NULL)
780     {
781       int first = 1;
782       FILE *fp;
783
784       /* Open output file.  "-" or "/dev/stdout" means write to
785          standard output.  */
786       if (strcmp (header_name, "-") == 0
787           || strcmp (header_name, "/dev/stdout") == 0)
788         fp = stdout;
789       else
790         {
791           fp = fopen (header_name, "w");
792           if (fp == NULL)
793             error (EXIT_FAILURE, errno,
794                    gettext ("cannot open output file `%s'"), header_name);
795         }
796
797       /* Iterate over all sets and all messages.  */
798       set_run = catalog->all_sets;
799       while (set_run != NULL)
800         {
801           struct message_list *message_run;
802
803           /* If the current message set has a symbolic name write this
804              out first.  */
805           if (set_run->symbol != NULL)
806             fprintf (fp, "%s#define %sSet %#x\t/* %s:%u */\n",
807                      first ? "" : "\n", set_run->symbol, set_run->number - 1,
808                      set_run->fname, set_run->line);
809           first = 0;
810
811           message_run = set_run->messages;
812           while (message_run != NULL)
813             {
814               /* If the current message has a symbolic name write
815                  #define out.  But we have to take care for the set
816                  not having a symbolic name.  */
817               if (message_run->symbol != NULL)
818                 if (set_run->symbol == NULL)
819                   fprintf (fp, "#define AutomaticSet%d%s %#x\t/* %s:%u */\n",
820                            set_run->number, message_run->symbol,
821                            message_run->number, message_run->fname,
822                            message_run->line);
823                 else
824                   fprintf (fp, "#define %s%s %#x\t/* %s:%u */\n",
825                            set_run->symbol, message_run->symbol,
826                            message_run->number, message_run->fname,
827                            message_run->line);
828
829               message_run = message_run->next;
830             }
831
832           set_run = set_run->next;
833         }
834
835       if (fp != stdout)
836         fclose (fp);
837     }
838 }
839
840
841 static struct set_list *
842 find_set (struct catalog *current, int number)
843 {
844   struct set_list *result = current->all_sets;
845
846   /* We must avoid set number 0 because a set of this number signals
847      in the tables that the entry is not occupied.  */
848   ++number;
849
850   while (result != NULL)
851     if (result->number == number)
852       return result;
853     else
854       result = result->next;
855
856   /* Prepare new message set.  */
857   result = (struct set_list *) xmalloc (sizeof (*result));
858   result->number = number;
859   result->deleted = 0;
860   result->messages = NULL;
861   result->next = current->all_sets;
862   current->all_sets = result;
863
864   return result;
865 }
866
867
868 /* Normalize given string *in*place* by processing escape sequences
869    and quote characters.  */
870 static void
871 normalize_line (const char *fname, size_t line, char *string, char quote_char)
872 {
873   int is_quoted;
874   char *rp = string;
875   char *wp = string;
876
877   if (quote_char != '\0' && *rp == quote_char)
878     {
879       is_quoted = 1;
880       ++rp;
881     }
882   else
883     is_quoted = 0;
884
885   while (*rp != '\0')
886     if (*rp == quote_char)
887       /* We simply end the string when we find the first time an
888          not-escaped quote character.  */
889         break;
890     else if (*rp == '\\')
891       {
892         ++rp;
893         if (quote_char != '\0' && *rp == quote_char)
894           /* This is an extension to XPG.  */
895           *wp++ = *rp++;
896         else
897           /* Recognize escape sequences.  */
898           switch (*rp)
899             {
900             case 'n':
901               *wp++ = '\n';
902               ++rp;
903               break;
904             case 't':
905               *wp++ = '\t';
906               ++rp;
907               break;
908             case 'v':
909               *wp++ = '\v';
910               ++rp;
911               break;
912             case 'b':
913               *wp++ = '\b';
914               ++rp;
915               break;
916             case 'r':
917               *wp++ = '\r';
918               ++rp;
919               break;
920             case 'f':
921               *wp++ = '\f';
922               ++rp;
923               break;
924             case '\\':
925               *wp++ = '\\';
926               ++rp;
927               break;
928             case '0' ... '7':
929               {
930                 int number = *rp++ - '0';
931                 while (number <= (255 / 8) && *rp >= '0' && *rp <= '7')
932                   {
933                     number *= 8;
934                     number += *rp++ - '0';
935                   }
936                 *wp++ = (char) number;
937               }
938               break;
939             default:
940               /* Simply ignore the backslash character.  */
941               break;
942             }
943       }
944     else
945       *wp++ = *rp++;
946
947   /* If we saw a quote character at the beginning we expect another
948      one at the end.  */
949   if (is_quoted && *rp != quote_char)
950     error (0, 0, fname, line, gettext ("unterminated message"));
951
952   /* Terminate string.  */
953   *wp = '\0';
954   return;
955 }
956
957
958 static void
959 read_old (struct catalog *catalog, const char *file_name)
960 {
961   struct catalog_info old_cat_obj;
962   struct set_list *set = NULL;
963   int last_set = -1;
964   size_t cnt;
965
966   old_cat_obj.status = closed;
967   old_cat_obj.cat_name = file_name;
968
969   /* Try to open catalog, but don't look through the NLSPATH.  */
970   __open_catalog (&old_cat_obj, 0);
971
972   if (old_cat_obj.status != mmaped && old_cat_obj.status != malloced)
973     if (errno == ENOENT)
974       /* No problem, the catalog simply does not exist.  */
975       return;
976     else
977       error (EXIT_FAILURE, errno, gettext ("while opening old catalog file"));
978
979   /* OK, we have the catalog loaded.  Now read all messages and merge
980      them.  When set and message number clash for any message the new
981      one is used.  */
982   for (cnt = 0; cnt < old_cat_obj.plane_size * old_cat_obj.plane_depth; ++cnt)
983     {
984       struct message_list *message, *last;
985
986       if (old_cat_obj.name_ptr[cnt * 3 + 0] == 0)
987         /* No message in this slot.  */
988         continue;
989
990       if (old_cat_obj.name_ptr[cnt * 3 + 0] - 1 != last_set)
991         {
992           last_set = old_cat_obj.name_ptr[cnt * 3 + 0] - 1;
993           set = find_set (catalog, old_cat_obj.name_ptr[cnt * 3 + 0] - 1);
994         }
995
996       last = NULL;
997       message = set->messages;
998       while (message != NULL)
999         {
1000           if (message->number >= old_cat_obj.name_ptr[cnt * 3 + 1])
1001             break;
1002           last = message;
1003           message = message->next;
1004         }
1005
1006       if (message == NULL
1007           || message->number > old_cat_obj.name_ptr[cnt * 3 + 1])
1008         {
1009           /* We have found a message which is not yet in the catalog.
1010              Insert it at the right position.  */
1011           struct message_list *newp;
1012
1013           newp = (struct message_list *) xmalloc (sizeof(*newp));
1014           newp->number = old_cat_obj.name_ptr[cnt * 3 + 1];
1015           newp->message =
1016             &old_cat_obj.strings[old_cat_obj.name_ptr[cnt * 3 + 2]];
1017           newp->fname = NULL;
1018           newp->line = 0;
1019           newp->symbol = NULL;
1020           newp->next = message;
1021
1022           if (last == NULL)
1023             set->messages = newp;
1024           else
1025             last->next = newp;
1026
1027           ++catalog->total_messages;
1028         }
1029     }
1030 }