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