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