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