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