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