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