1 /* GNU gettext - internationalization aids
2    Copyright (C) 1995-1998, 2000-2010, 2012, 2014-2016, 2018-2020 Free Software
3    Foundation, Inc.
4    This file was written by Peter Miller <millerp@canb.auug.org.au>
5 
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
18 
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22 #include <alloca.h>
23 
24 #include <getopt.h>
25 #include <limits.h>
26 #include <stdbool.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <locale.h>
31 #ifdef _OPENMP
32 # include <omp.h>
33 #endif
34 
35 #include <textstyle.h>
36 
37 #include "noreturn.h"
38 #include "closeout.h"
39 #include "dir-list.h"
40 #include "error.h"
41 #include "error-progname.h"
42 #include "progname.h"
43 #include "relocatable.h"
44 #include "basename-lgpl.h"
45 #include "message.h"
46 #include "read-catalog.h"
47 #include "read-po.h"
48 #include "read-properties.h"
49 #include "read-stringtable.h"
50 #include "write-catalog.h"
51 #include "write-po.h"
52 #include "write-properties.h"
53 #include "write-stringtable.h"
54 #include "format.h"
55 #include "xalloc.h"
56 #include "xmalloca.h"
57 #include "obstack.h"
58 #include "c-strstr.h"
59 #include "c-strcase.h"
60 #include "po-charset.h"
61 #include "msgl-iconv.h"
62 #include "msgl-equal.h"
63 #include "msgl-fsearch.h"
64 #include "glthread/lock.h"
65 #include "lang-table.h"
66 #include "plural-exp.h"
67 #include "plural-count.h"
68 #include "msgl-check.h"
69 #include "po-xerror.h"
70 #include "backupfile.h"
71 #include "copy-file.h"
72 #include "propername.h"
73 #include "gettext.h"
74 
75 #define _(str) gettext (str)
76 
77 #define obstack_chunk_alloc xmalloc
78 #define obstack_chunk_free free
79 
80 
81 /* If true do not print unneeded messages.  */
82 static bool quiet;
83 
84 /* Verbosity level.  */
85 static int verbosity_level;
86 
87 /* Force output of PO file even if empty.  */
88 static int force_po;
89 
90 /* Apply the .pot file to each of the domains in the PO file.  */
91 static bool multi_domain_mode = false;
92 
93 /* Produce output for msgfmt, not for a translator.
94    msgfmt ignores
95      - untranslated messages,
96      - fuzzy messages, except the header entry,
97      - obsolete messages.
98    Therefore output for msgfmt does not need to include such messages.  */
99 static bool for_msgfmt = false;
100 
101 /* Determines whether to use fuzzy matching.  */
102 static bool use_fuzzy_matching = true;
103 
104 /* Determines whether to keep old msgids as previous msgids.  */
105 static bool keep_previous = false;
106 
107 /* Language (ISO-639 code) and optional territory (ISO-3166 code).  */
108 static const char *catalogname = NULL;
109 
110 /* List of user-specified compendiums.  */
111 static message_list_list_ty *compendiums;
112 
113 /* List of corresponding filenames.  */
114 static string_list_ty *compendium_filenames;
115 
116 /* Update mode.  */
117 static bool update_mode = false;
118 static const char *version_control_string;
119 static const char *backup_suffix_string;
120 
121 /* Long options.  */
122 static const struct option long_options[] =
123 {
124   { "add-location", optional_argument, NULL, 'n' },
125   { "backup", required_argument, NULL, CHAR_MAX + 1 },
126   { "color", optional_argument, NULL, CHAR_MAX + 9 },
127   { "compendium", required_argument, NULL, 'C' },
128   { "directory", required_argument, NULL, 'D' },
129   { "escape", no_argument, NULL, 'E' },
130   { "for-msgfmt", no_argument, NULL, CHAR_MAX + 12 },
131   { "force-po", no_argument, &force_po, 1 },
132   { "help", no_argument, NULL, 'h' },
133   { "indent", no_argument, NULL, 'i' },
134   { "lang", required_argument, NULL, CHAR_MAX + 8 },
135   { "multi-domain", no_argument, NULL, 'm' },
136   { "no-escape", no_argument, NULL, 'e' },
137   { "no-fuzzy-matching", no_argument, NULL, 'N' },
138   { "no-location", no_argument, NULL, CHAR_MAX + 11 },
139   { "no-wrap", no_argument, NULL, CHAR_MAX + 4 },
140   { "output-file", required_argument, NULL, 'o' },
141   { "previous", no_argument, NULL, CHAR_MAX + 7 },
142   { "properties-input", no_argument, NULL, 'P' },
143   { "properties-output", no_argument, NULL, 'p' },
144   { "quiet", no_argument, NULL, 'q' },
145   { "sort-by-file", no_argument, NULL, 'F' },
146   { "sort-output", no_argument, NULL, 's' },
147   { "silent", no_argument, NULL, 'q' },
148   { "strict", no_argument, NULL, CHAR_MAX + 2 },
149   { "stringtable-input", no_argument, NULL, CHAR_MAX + 5 },
150   { "stringtable-output", no_argument, NULL, CHAR_MAX + 6 },
151   { "style", required_argument, NULL, CHAR_MAX + 10 },
152   { "suffix", required_argument, NULL, CHAR_MAX + 3 },
153   { "update", no_argument, NULL, 'U' },
154   { "verbose", no_argument, NULL, 'v' },
155   { "version", no_argument, NULL, 'V' },
156   { "width", required_argument, NULL, 'w' },
157   { NULL, 0, NULL, 0 }
158 };
159 
160 
161 struct statistics
162 {
163   size_t merged;
164   size_t fuzzied;
165   size_t missing;
166   size_t obsolete;
167 };
168 
169 
170 /* Forward declaration of local functions.  */
171 _GL_NORETURN_FUNC static void usage (int status);
172 static void compendium (const char *filename);
173 static void msgdomain_list_stablesort_by_obsolete (msgdomain_list_ty *mdlp);
174 static msgdomain_list_ty *merge (const char *fn1, const char *fn2,
175                                  catalog_input_format_ty input_syntax,
176                                  msgdomain_list_ty **defp);
177 
178 
179 int
main(int argc,char ** argv)180 main (int argc, char **argv)
181 {
182   int opt;
183   bool do_help;
184   bool do_version;
185   char *output_file;
186   char *color;
187   msgdomain_list_ty *def;
188   msgdomain_list_ty *result;
189   catalog_input_format_ty input_syntax = &input_format_po;
190   catalog_output_format_ty output_syntax = &output_format_po;
191   bool sort_by_filepos = false;
192   bool sort_by_msgid = false;
193 
194   /* Set program name for messages.  */
195   set_program_name (argv[0]);
196   error_print_progname = maybe_print_progname;
197   verbosity_level = 0;
198   quiet = false;
199   gram_max_allowed_errors = UINT_MAX;
200 
201   /* Set locale via LC_ALL.  */
202   setlocale (LC_ALL, "");
203 
204   /* Set the text message domain.  */
205   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
206   bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
207   textdomain (PACKAGE);
208 
209   /* Ensure that write errors on stdout are detected.  */
210   atexit (close_stdout);
211 
212   /* Set default values for variables.  */
213   do_help = false;
214   do_version = false;
215   output_file = NULL;
216   color = NULL;
217 
218   while ((opt = getopt_long (argc, argv, "C:D:eEFhimn:No:pPqsUvVw:",
219                              long_options, NULL))
220          != EOF)
221     switch (opt)
222       {
223       case '\0':                /* Long option.  */
224         break;
225 
226       case 'C':
227         compendium (optarg);
228         break;
229 
230       case 'D':
231         dir_list_append (optarg);
232         break;
233 
234       case 'e':
235         message_print_style_escape (false);
236         break;
237 
238       case 'E':
239         message_print_style_escape (true);
240         break;
241 
242       case 'F':
243         sort_by_filepos = true;
244         break;
245 
246       case 'h':
247         do_help = true;
248         break;
249 
250       case 'i':
251         message_print_style_indent ();
252         break;
253 
254       case 'm':
255         multi_domain_mode = true;
256         break;
257 
258       case 'n':
259         if (handle_filepos_comment_option (optarg))
260           usage (EXIT_FAILURE);
261         break;
262 
263       case 'N':
264         use_fuzzy_matching = false;
265         break;
266 
267       case 'o':
268         output_file = optarg;
269         break;
270 
271       case 'p':
272         output_syntax = &output_format_properties;
273         break;
274 
275       case 'P':
276         input_syntax = &input_format_properties;
277         break;
278 
279       case 'q':
280         quiet = true;
281         break;
282 
283       case 's':
284         sort_by_msgid = true;
285         break;
286 
287       case 'U':
288         update_mode = true;
289         break;
290 
291       case 'v':
292         ++verbosity_level;
293         break;
294 
295       case 'V':
296         do_version = true;
297         break;
298 
299       case 'w':
300         {
301           int value;
302           char *endp;
303           value = strtol (optarg, &endp, 10);
304           if (endp != optarg)
305             message_page_width_set (value);
306         }
307         break;
308 
309       case CHAR_MAX + 1: /* --backup */
310         version_control_string = optarg;
311         break;
312 
313       case CHAR_MAX + 2: /* --strict */
314         message_print_style_uniforum ();
315         break;
316 
317       case CHAR_MAX + 3: /* --suffix */
318         backup_suffix_string = optarg;
319         break;
320 
321       case CHAR_MAX + 4: /* --no-wrap */
322         message_page_width_ignore ();
323         break;
324 
325       case CHAR_MAX + 5: /* --stringtable-input */
326         input_syntax = &input_format_stringtable;
327         break;
328 
329       case CHAR_MAX + 6: /* --stringtable-output */
330         output_syntax = &output_format_stringtable;
331         break;
332 
333       case CHAR_MAX + 7: /* --previous */
334         keep_previous = true;
335         break;
336 
337       case CHAR_MAX + 8: /* --lang */
338         catalogname = optarg;
339         break;
340 
341       case CHAR_MAX + 9: /* --color */
342         if (handle_color_option (optarg) || color_test_mode)
343           usage (EXIT_FAILURE);
344         color = optarg;
345         break;
346 
347       case CHAR_MAX + 10: /* --style */
348         handle_style_option (optarg);
349         break;
350 
351       case CHAR_MAX + 11: /* --no-location */
352         message_print_style_filepos (filepos_comment_none);
353         break;
354 
355       case CHAR_MAX + 12: /* --for-msgfmt */
356         for_msgfmt = true;
357         break;
358 
359       default:
360         usage (EXIT_FAILURE);
361         break;
362       }
363 
364   /* Version information is requested.  */
365   if (do_version)
366     {
367       printf ("%s (GNU %s) %s\n", last_component (program_name),
368               PACKAGE, VERSION);
369       /* xgettext: no-wrap */
370       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
371 License GPLv3+: GNU GPL version 3 or later <%s>\n\
372 This is free software: you are free to change and redistribute it.\n\
373 There is NO WARRANTY, to the extent permitted by law.\n\
374 "),
375               "1995-2020", "https://gnu.org/licenses/gpl.html");
376       printf (_("Written by %s.\n"), proper_name ("Peter Miller"));
377       exit (EXIT_SUCCESS);
378     }
379 
380   /* Help is requested.  */
381   if (do_help)
382     usage (EXIT_SUCCESS);
383 
384   /* Test whether we have an .po file name as argument.  */
385   if (optind >= argc)
386     {
387       error (EXIT_SUCCESS, 0, _("no input files given"));
388       usage (EXIT_FAILURE);
389     }
390   if (optind + 2 != argc)
391     {
392       error (EXIT_SUCCESS, 0, _("exactly 2 input files required"));
393       usage (EXIT_FAILURE);
394     }
395 
396   /* Verify selected options.  */
397   if (update_mode)
398     {
399       if (output_file != NULL)
400         {
401           error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
402                  "--update", "--output-file");
403         }
404       if (for_msgfmt)
405         {
406           error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
407                  "--update", "--for-msgfmt");
408         }
409       if (color != NULL)
410         {
411           error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
412                  "--update", "--color");
413         }
414       if (style_file_name != NULL)
415         {
416           error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
417                  "--update", "--style");
418         }
419     }
420   else
421     {
422       if (version_control_string != NULL)
423         {
424           error (EXIT_SUCCESS, 0, _("%s is only valid with %s"),
425                  "--backup", "--update");
426           usage (EXIT_FAILURE);
427         }
428       if (backup_suffix_string != NULL)
429         {
430           error (EXIT_SUCCESS, 0, _("%s is only valid with %s"),
431                  "--suffix", "--update");
432           usage (EXIT_FAILURE);
433         }
434     }
435 
436   if (sort_by_msgid && sort_by_filepos)
437     error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
438            "--sort-output", "--sort-by-file");
439 
440   /* In update mode, --properties-input implies --properties-output.  */
441   if (update_mode && input_syntax == &input_format_properties)
442     output_syntax = &output_format_properties;
443   /* In update mode, --stringtable-input implies --stringtable-output.  */
444   if (update_mode && input_syntax == &input_format_stringtable)
445     output_syntax = &output_format_stringtable;
446 
447   if (for_msgfmt)
448     {
449       /* With --for-msgfmt, no fuzzy matching.  */
450       use_fuzzy_matching = false;
451 
452       /* With --for-msgfmt, merging is fast, therefore no need for a progress
453          indicator.  */
454       quiet = true;
455 
456       /* With --for-msgfmt, no need for comments.  */
457       message_print_style_comment (false);
458 
459       /* With --for-msgfmt, no need for source location lines.  */
460       message_print_style_filepos (filepos_comment_none);
461     }
462 
463   /* Initialize OpenMP.  */
464   #ifdef _OPENMP
465   openmp_init ();
466   #endif
467 
468   /* Merge the two files.  */
469   result = merge (argv[optind], argv[optind + 1], input_syntax, &def);
470 
471   /* Sort the results.  */
472   if (sort_by_filepos)
473     msgdomain_list_sort_by_filepos (result);
474   else if (sort_by_msgid)
475     msgdomain_list_sort_by_msgid (result);
476 
477   if (update_mode)
478     {
479       /* Before comparing result with def, sort the result into the same order
480          as would be done implicitly by output_syntax->print.  */
481       if (output_syntax->sorts_obsoletes_to_end)
482         msgdomain_list_stablesort_by_obsolete (result);
483 
484       /* Do nothing if the original file and the result are equal.  Also do
485          nothing if the original file and the result differ only by the
486          POT-Creation-Date in the header entry; this is needed for projects
487          which don't put the .pot file under CVS.  */
488       if (!msgdomain_list_equal (def, result, true))
489         {
490           /* Back up def.po.  */
491           enum backup_type backup_type;
492           char *backup_file;
493 
494           output_file = argv[optind];
495 
496           if (backup_suffix_string == NULL)
497             {
498               backup_suffix_string = getenv ("SIMPLE_BACKUP_SUFFIX");
499               if (backup_suffix_string != NULL
500                   && backup_suffix_string[0] == '\0')
501                 backup_suffix_string = NULL;
502             }
503           if (backup_suffix_string != NULL)
504             simple_backup_suffix = backup_suffix_string;
505 
506           backup_type = xget_version (_("backup type"), version_control_string);
507           if (backup_type != none)
508             {
509               backup_file = find_backup_file_name (output_file, backup_type);
510               copy_file_preserving (output_file, backup_file);
511             }
512 
513           /* Write the merged message list out.  */
514           msgdomain_list_print (result, output_file, output_syntax, true,
515                                 false);
516         }
517     }
518   else
519     {
520       /* Write the merged message list out.  */
521       msgdomain_list_print (result, output_file, output_syntax,
522                             for_msgfmt || force_po, false);
523     }
524 
525   exit (EXIT_SUCCESS);
526 }
527 
528 
529 /* Display usage information and exit.  */
530 static void
usage(int status)531 usage (int status)
532 {
533   if (status != EXIT_SUCCESS)
534     fprintf (stderr, _("Try '%s --help' for more information.\n"),
535              program_name);
536   else
537     {
538       printf (_("\
539 Usage: %s [OPTION] def.po ref.pot\n\
540 "), program_name);
541       printf ("\n");
542       /* xgettext: no-wrap */
543       printf (_("\
544 Merges two Uniforum style .po files together.  The def.po file is an\n\
545 existing PO file with translations which will be taken over to the newly\n\
546 created file as long as they still match; comments will be preserved,\n\
547 but extracted comments and file positions will be discarded.  The ref.pot\n\
548 file is the last created PO file with up-to-date source references but\n\
549 old translations, or a PO Template file (generally created by xgettext);\n\
550 any translations or comments in the file will be discarded, however dot\n\
551 comments and file positions will be preserved.  Where an exact match\n\
552 cannot be found, fuzzy matching is used to produce better results.\n\
553 "));
554       printf ("\n");
555       printf (_("\
556 Mandatory arguments to long options are mandatory for short options too.\n"));
557       printf ("\n");
558       printf (_("\
559 Input file location:\n"));
560       printf (_("\
561   def.po                      translations referring to old sources\n"));
562       printf (_("\
563   ref.pot                     references to new sources\n"));
564       printf (_("\
565   -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
566       printf (_("\
567   -C, --compendium=FILE       additional library of message translations,\n\
568                               may be specified more than once\n"));
569       printf ("\n");
570       printf (_("\
571 Operation mode:\n"));
572       printf (_("\
573   -U, --update                update def.po,\n\
574                               do nothing if def.po already up to date\n"));
575       printf ("\n");
576       printf (_("\
577 Output file location:\n"));
578       printf (_("\
579   -o, --output-file=FILE      write output to specified file\n"));
580       printf (_("\
581 The results are written to standard output if no output file is specified\n\
582 or if it is -.\n"));
583       printf ("\n");
584       printf (_("\
585 Output file location in update mode:\n"));
586       printf (_("\
587 The result is written back to def.po.\n"));
588       printf (_("\
589       --backup=CONTROL        make a backup of def.po\n"));
590       printf (_("\
591       --suffix=SUFFIX         override the usual backup suffix\n"));
592       printf (_("\
593 The version control method may be selected via the --backup option or through\n\
594 the VERSION_CONTROL environment variable.  Here are the values:\n\
595   none, off       never make backups (even if --backup is given)\n\
596   numbered, t     make numbered backups\n\
597   existing, nil   numbered if numbered backups exist, simple otherwise\n\
598   simple, never   always make simple backups\n"));
599       printf (_("\
600 The backup suffix is '~', unless set with --suffix or the SIMPLE_BACKUP_SUFFIX\n\
601 environment variable.\n\
602 "));
603       printf ("\n");
604       printf (_("\
605 Operation modifiers:\n"));
606       printf (_("\
607   -m, --multi-domain          apply ref.pot to each of the domains in def.po\n"));
608       printf (_("\
609       --for-msgfmt            produce output for '%s', not for a translator\n"),
610               "msgfmt");
611       printf (_("\
612   -N, --no-fuzzy-matching     do not use fuzzy matching\n"));
613       printf (_("\
614       --previous              keep previous msgids of translated messages\n"));
615       printf ("\n");
616       printf (_("\
617 Input file syntax:\n"));
618       printf (_("\
619   -P, --properties-input      input files are in Java .properties syntax\n"));
620       printf (_("\
621       --stringtable-input     input files are in NeXTstep/GNUstep .strings\n\
622                               syntax\n"));
623       printf ("\n");
624       printf (_("\
625 Output details:\n"));
626       printf (_("\
627       --lang=CATALOGNAME      set 'Language' field in the header entry\n"));
628       printf (_("\
629       --color                 use colors and other text attributes always\n\
630       --color=WHEN            use colors and other text attributes if WHEN.\n\
631                               WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
632       printf (_("\
633       --style=STYLEFILE       specify CSS style rule file for --color\n"));
634       printf (_("\
635   -e, --no-escape             do not use C escapes in output (default)\n"));
636       printf (_("\
637   -E, --escape                use C escapes in output, no extended chars\n"));
638       printf (_("\
639       --force-po              write PO file even if empty\n"));
640       printf (_("\
641   -i, --indent                indented output style\n"));
642       printf (_("\
643       --no-location           suppress '#: filename:line' lines\n"));
644       printf (_("\
645   -n, --add-location          preserve '#: filename:line' lines (default)\n"));
646       printf (_("\
647       --strict                strict Uniforum output style\n"));
648       printf (_("\
649   -p, --properties-output     write out a Java .properties file\n"));
650       printf (_("\
651       --stringtable-output    write out a NeXTstep/GNUstep .strings file\n"));
652       printf (_("\
653   -w, --width=NUMBER          set output page width\n"));
654       printf (_("\
655       --no-wrap               do not break long message lines, longer than\n\
656                               the output page width, into several lines\n"));
657       printf (_("\
658   -s, --sort-output           generate sorted output\n"));
659       printf (_("\
660   -F, --sort-by-file          sort output by file location\n"));
661       printf ("\n");
662       printf (_("\
663 Informative output:\n"));
664       printf (_("\
665   -h, --help                  display this help and exit\n"));
666       printf (_("\
667   -V, --version               output version information and exit\n"));
668       printf (_("\
669   -v, --verbose               increase verbosity level\n"));
670       printf (_("\
671   -q, --quiet, --silent       suppress progress indicators\n"));
672       printf ("\n");
673       /* TRANSLATORS: The first placeholder is the web address of the Savannah
674          project of this package.  The second placeholder is the bug-reporting
675          email address for this package.  Please add _another line_ saying
676          "Report translation bugs to <...>\n" with the address for translation
677          bugs (typically your translation team's web or email address).  */
678       printf(_("\
679 Report bugs in the bug tracker at <%s>\n\
680 or by email to <%s>.\n"),
681              "https://savannah.gnu.org/projects/gettext",
682              "bug-gettext@gnu.org");
683     }
684 
685   exit (status);
686 }
687 
688 
689 static void
compendium(const char * filename)690 compendium (const char *filename)
691 {
692   msgdomain_list_ty *mdlp;
693   size_t k;
694 
695   mdlp = read_catalog_file (filename, &input_format_po);
696   if (compendiums == NULL)
697     {
698       compendiums = message_list_list_alloc ();
699       compendium_filenames = string_list_alloc ();
700     }
701   for (k = 0; k < mdlp->nitems; k++)
702     {
703       message_list_list_append (compendiums, mdlp->item[k]->messages);
704       string_list_append (compendium_filenames, filename);
705     }
706 }
707 
708 
709 /* Sorts obsolete messages to the end, for every domain.  */
710 static void
msgdomain_list_stablesort_by_obsolete(msgdomain_list_ty * mdlp)711 msgdomain_list_stablesort_by_obsolete (msgdomain_list_ty *mdlp)
712 {
713   size_t k;
714 
715   for (k = 0; k < mdlp->nitems; k++)
716     {
717       message_list_ty *mlp = mdlp->item[k]->messages;
718 
719       /* Sort obsolete messages to the end.  */
720       if (mlp->nitems > 0)
721         {
722           message_ty **l1 = XNMALLOC (mlp->nitems, message_ty *);
723           size_t n1;
724           message_ty **l2 = XNMALLOC (mlp->nitems, message_ty *);
725           size_t n2;
726           size_t j;
727 
728           /* Sort the non-obsolete messages into l1 and the obsolete messages
729              into l2.  */
730           n1 = 0;
731           n2 = 0;
732           for (j = 0; j < mlp->nitems; j++)
733             {
734               message_ty *mp = mlp->item[j];
735 
736               if (mp->obsolete)
737                 l2[n2++] = mp;
738               else
739                 l1[n1++] = mp;
740             }
741           if (n1 > 0 && n2 > 0)
742             {
743               memcpy (mlp->item, l1, n1 * sizeof (message_ty *));
744               memcpy (mlp->item + n1, l2, n2 * sizeof (message_ty *));
745             }
746           free (l2);
747           free (l1);
748         }
749     }
750 }
751 
752 
753 /* Data structure representing the messages with known translations.
754    They are composed of
755      - A message list from def.po,
756      - The compendiums.
757    The data structure is optimized for exact and fuzzy searches.  */
758 typedef struct definitions_ty definitions_ty;
759 struct definitions_ty
760 {
761   /* A list of message lists.  The first comes from def.po, the other ones
762      from the compendiums.  Each message list has a built-in hash table,
763      for speed when doing the exact searches.  */
764   message_list_list_ty *lists;
765 
766   /* A fuzzy index of the current list of non-compendium messages, for speed
767      when doing fuzzy searches.  Used only if use_fuzzy_matching is true.  */
768   message_fuzzy_index_ty *curr_findex;
769   /* A once-only execution guard for the initialization of the fuzzy index.
770      Needed for OpenMP.  */
771   gl_lock_define(, curr_findex_init_lock)
772 
773   /* A fuzzy index of the compendiums, for speed when doing fuzzy searches.
774      Used only if use_fuzzy_matching is true and compendiums != NULL.  */
775   message_fuzzy_index_ty *comp_findex;
776   /* A once-only execution guard for the initialization of the fuzzy index.
777      Needed for OpenMP.  */
778   gl_lock_define(, comp_findex_init_lock)
779 
780   /* The canonical encoding of the definitions and the compendiums.
781      Only used for fuzzy matching.  */
782   const char *canon_charset;
783 };
784 
785 static inline void
definitions_init(definitions_ty * definitions,const char * canon_charset)786 definitions_init (definitions_ty *definitions, const char *canon_charset)
787 {
788   definitions->lists = message_list_list_alloc ();
789   message_list_list_append (definitions->lists, NULL);
790   if (compendiums != NULL)
791     message_list_list_append_list (definitions->lists, compendiums);
792   definitions->curr_findex = NULL;
793   gl_lock_init (definitions->curr_findex_init_lock);
794   definitions->comp_findex = NULL;
795   gl_lock_init (definitions->comp_findex_init_lock);
796   definitions->canon_charset = canon_charset;
797 }
798 
799 /* Return the current list of non-compendium messages.  */
800 static inline message_list_ty *
definitions_current_list(const definitions_ty * definitions)801 definitions_current_list (const definitions_ty *definitions)
802 {
803   return definitions->lists->item[0];
804 }
805 
806 /* Set the current list of non-compendium messages.  */
807 static inline void
definitions_set_current_list(definitions_ty * definitions,message_list_ty * mlp)808 definitions_set_current_list (definitions_ty *definitions, message_list_ty *mlp)
809 {
810   definitions->lists->item[0] = mlp;
811   if (definitions->curr_findex != NULL)
812     {
813       message_fuzzy_index_free (definitions->curr_findex);
814       definitions->curr_findex = NULL;
815     }
816 }
817 
818 /* Create the fuzzy index for the current list of non-compendium messages.
819    Used only if use_fuzzy_matching is true.  */
820 static inline void
definitions_init_curr_findex(definitions_ty * definitions)821 definitions_init_curr_findex (definitions_ty *definitions)
822 {
823   /* Protect against concurrent execution.  */
824   gl_lock_lock (definitions->curr_findex_init_lock);
825   if (definitions->curr_findex == NULL)
826     definitions->curr_findex =
827       message_fuzzy_index_alloc (definitions_current_list (definitions),
828                                  definitions->canon_charset);
829   gl_lock_unlock (definitions->curr_findex_init_lock);
830 }
831 
832 /* Create the fuzzy index for the compendium messages.
833    Used only if use_fuzzy_matching is true and compendiums != NULL.  */
834 static inline void
definitions_init_comp_findex(definitions_ty * definitions)835 definitions_init_comp_findex (definitions_ty *definitions)
836 {
837   /* Protect against concurrent execution.  */
838   gl_lock_lock (definitions->comp_findex_init_lock);
839   if (definitions->comp_findex == NULL)
840     {
841       /* Combine all the compendium message lists into a single one.  Don't
842          bother checking for duplicates.  */
843       message_list_ty *all_compendium;
844       size_t i;
845 
846       all_compendium = message_list_alloc (false);
847       for (i = 0; i < compendiums->nitems; i++)
848         {
849           message_list_ty *mlp = compendiums->item[i];
850           size_t j;
851 
852           for (j = 0; j < mlp->nitems; j++)
853             message_list_append (all_compendium, mlp->item[j]);
854         }
855 
856       /* Create the fuzzy index from it.  */
857       definitions->comp_findex =
858         message_fuzzy_index_alloc (all_compendium, definitions->canon_charset);
859     }
860   gl_lock_unlock (definitions->comp_findex_init_lock);
861 }
862 
863 /* Exact search.  */
864 static inline message_ty *
definitions_search(const definitions_ty * definitions,const char * msgctxt,const char * msgid)865 definitions_search (const definitions_ty *definitions,
866                     const char *msgctxt, const char *msgid)
867 {
868   return message_list_list_search (definitions->lists, msgctxt, msgid);
869 }
870 
871 /* Fuzzy search.
872    Used only if use_fuzzy_matching is true.  */
873 static inline message_ty *
definitions_search_fuzzy(definitions_ty * definitions,const char * msgctxt,const char * msgid)874 definitions_search_fuzzy (definitions_ty *definitions,
875                           const char *msgctxt, const char *msgid)
876 {
877   message_ty *mp1;
878 
879   if (false)
880     {
881       /* Old, slow code.  */
882       mp1 =
883         message_list_search_fuzzy (definitions_current_list (definitions),
884                                    msgctxt, msgid);
885     }
886   else
887     {
888       /* Speedup through early abort in fstrcmp(), combined with pre-sorting
889          of the messages through a hashed index.  */
890       /* Create the fuzzy index lazily.  */
891       if (definitions->curr_findex == NULL)
892         definitions_init_curr_findex (definitions);
893       mp1 = message_fuzzy_index_search (definitions->curr_findex,
894                                         msgctxt, msgid,
895                                         FUZZY_THRESHOLD, false);
896     }
897 
898   if (compendiums != NULL)
899     {
900       double lower_bound_for_mp2;
901       message_ty *mp2;
902 
903       lower_bound_for_mp2 =
904         (mp1 != NULL
905          ? fuzzy_search_goal_function (mp1, msgctxt, msgid, 0.0)
906          : FUZZY_THRESHOLD);
907       /* This lower bound must be >= FUZZY_THRESHOLD.  */
908       if (!(lower_bound_for_mp2 >= FUZZY_THRESHOLD))
909         abort ();
910 
911       /* Create the fuzzy index lazily.  */
912       if (definitions->comp_findex == NULL)
913         definitions_init_comp_findex (definitions);
914 
915       mp2 = message_fuzzy_index_search (definitions->comp_findex,
916                                         msgctxt, msgid,
917                                         lower_bound_for_mp2, true);
918 
919       /* Choose the best among mp1, mp2.  */
920       if (mp1 == NULL
921           || (mp2 != NULL
922               && (fuzzy_search_goal_function (mp2, msgctxt, msgid,
923                                               lower_bound_for_mp2)
924                   > lower_bound_for_mp2)))
925         mp1 = mp2;
926     }
927 
928   return mp1;
929 }
930 
931 static inline void
definitions_destroy(definitions_ty * definitions)932 definitions_destroy (definitions_ty *definitions)
933 {
934   message_list_list_free (definitions->lists, 2);
935   if (definitions->curr_findex != NULL)
936     message_fuzzy_index_free (definitions->curr_findex);
937   if (definitions->comp_findex != NULL)
938     message_fuzzy_index_free (definitions->comp_findex);
939 }
940 
941 
942 /* A silent error logger.  We are only interested in knowing whether errors
943    occurred at all.  */
944 static void
945 silent_error_logger (const char *format, ...)
946      __attribute__ ((__format__ (__printf__, 1, 2)));
947 static void
silent_error_logger(const char * format,...)948 silent_error_logger (const char *format, ...)
949 {
950 }
951 
952 
953 /* Another silent error logger.  */
954 static void
silent_xerror(int severity,const struct message_ty * message,const char * filename,size_t lineno,size_t column,int multiline_p,const char * message_text)955 silent_xerror (int severity,
956                const struct message_ty *message,
957                const char *filename, size_t lineno, size_t column,
958                int multiline_p, const char *message_text)
959 {
960 }
961 
962 
963 static message_ty *
message_merge(message_ty * def,message_ty * ref,bool force_fuzzy,const struct plural_distribution * distribution)964 message_merge (message_ty *def, message_ty *ref, bool force_fuzzy,
965                const struct plural_distribution *distribution)
966 {
967   const char *msgstr;
968   size_t msgstr_len;
969   const char *prev_msgctxt;
970   const char *prev_msgid;
971   const char *prev_msgid_plural;
972   message_ty *result;
973   size_t j, i;
974 
975   /* Take the msgid from the reference.  When fuzzy matches are made,
976      the definition will not be unique, but the reference will be -
977      usually because it has only been slightly changed.  */
978 
979   /* Take the msgstr from the definition.  The msgstr of the reference
980      is usually empty, as it was generated by xgettext.  If we currently
981      process the header entry we have to merge the msgstr by using the
982      Report-Msgid-Bugs-To and POT-Creation-Date fields from the reference.  */
983   if (is_header (ref))
984     {
985       /* Oh, oh.  The header entry and we have something to fill in.  */
986       static const struct
987       {
988         const char *name;
989         size_t len;
990       } known_fields[] =
991       {
992         { "Project-Id-Version:", sizeof ("Project-Id-Version:") - 1 },
993 #define PROJECT_ID              0
994         { "Report-Msgid-Bugs-To:", sizeof ("Report-Msgid-Bugs-To:") - 1 },
995 #define REPORT_MSGID_BUGS_TO    1
996         { "POT-Creation-Date:", sizeof ("POT-Creation-Date:") - 1 },
997 #define POT_CREATION_DATE       2
998         { "PO-Revision-Date:", sizeof ("PO-Revision-Date:") - 1 },
999 #define PO_REVISION_DATE        3
1000         { "Last-Translator:", sizeof ("Last-Translator:") - 1 },
1001 #define LAST_TRANSLATOR         4
1002         { "Language-Team:", sizeof ("Language-Team:") - 1 },
1003 #define LANGUAGE_TEAM           5
1004         { "Language:", sizeof ("Language:") - 1 },
1005 #define LANGUAGE                6
1006         { "MIME-Version:", sizeof ("MIME-Version:") - 1 },
1007 #define MIME_VERSION            7
1008         { "Content-Type:", sizeof ("Content-Type:") - 1 },
1009 #define CONTENT_TYPE            8
1010         { "Content-Transfer-Encoding:",
1011           sizeof ("Content-Transfer-Encoding:") - 1 }
1012 #define CONTENT_TRANSFER        9
1013       };
1014 #define UNKNOWN 10
1015       struct
1016       {
1017         const char *string;
1018         size_t len;
1019       } header_fields[UNKNOWN + 1];
1020       struct obstack pool;
1021       const char *cp;
1022       char *newp;
1023       size_t len, cnt;
1024 
1025       /* Clear all fields.  */
1026       memset (header_fields, '\0', sizeof (header_fields));
1027 
1028       /* Prepare a temporary memory pool.  */
1029       obstack_init (&pool);
1030 
1031       cp = def->msgstr;
1032       while (*cp != '\0')
1033         {
1034           const char *endp = strchr (cp, '\n');
1035           int terminated = endp != NULL;
1036 
1037           if (!terminated)
1038             {
1039               /* Add a trailing newline.  */
1040               char *copy;
1041               endp = strchr (cp, '\0');
1042 
1043               len = endp - cp + 1;
1044 
1045               copy = (char *) obstack_alloc (&pool, len + 1);
1046               stpcpy (stpcpy (copy, cp), "\n");
1047               cp = copy;
1048             }
1049           else
1050             {
1051               len = (endp - cp) + 1;
1052               ++endp;
1053             }
1054 
1055           /* Compare with any of the known fields.  */
1056           for (cnt = 0;
1057                cnt < sizeof (known_fields) / sizeof (known_fields[0]);
1058                ++cnt)
1059             if (c_strncasecmp (cp, known_fields[cnt].name, known_fields[cnt].len)
1060                 == 0)
1061               break;
1062 
1063           if (cnt < sizeof (known_fields) / sizeof (known_fields[0]))
1064             {
1065               header_fields[cnt].string = &cp[known_fields[cnt].len];
1066               header_fields[cnt].len = len - known_fields[cnt].len;
1067             }
1068           else
1069             {
1070               /* It's an unknown field.  Append content to what is already
1071                  known.  */
1072               char *extended =
1073                 (char *) obstack_alloc (&pool,
1074                                         header_fields[UNKNOWN].len + len + 1);
1075               if (header_fields[UNKNOWN].string)
1076                 memcpy (extended, header_fields[UNKNOWN].string,
1077                         header_fields[UNKNOWN].len);
1078               memcpy (&extended[header_fields[UNKNOWN].len], cp, len);
1079               extended[header_fields[UNKNOWN].len + len] = '\0';
1080               header_fields[UNKNOWN].string = extended;
1081               header_fields[UNKNOWN].len += len;
1082             }
1083 
1084           cp = endp;
1085         }
1086 
1087       /* Set the Language field if specified on the command line.  */
1088       if (catalogname != NULL)
1089         {
1090           /* Prepend a space and append a newline.  */
1091           size_t len = strlen (catalogname);
1092           char *copy = (char *) obstack_alloc (&pool, 1 + len + 1 + 1);
1093           stpcpy (stpcpy (stpcpy (copy, " "), catalogname), "\n");
1094           header_fields[LANGUAGE].string = copy;
1095           header_fields[LANGUAGE].len = strlen (header_fields[LANGUAGE].string);
1096         }
1097       /* Add a Language field to PO files that don't have one.  The Language
1098          field was introduced in gettext-0.18.  */
1099       else if (header_fields[LANGUAGE].string == NULL)
1100         {
1101           const char *language_team_ptr = header_fields[LANGUAGE_TEAM].string;
1102 
1103           if (language_team_ptr != NULL)
1104             {
1105               size_t language_team_len = header_fields[LANGUAGE_TEAM].len;
1106 
1107               /* Trim leading blanks.  */
1108               while (language_team_len > 0
1109                      && (*language_team_ptr == ' '
1110                          || *language_team_ptr == '\t'))
1111                 {
1112                   language_team_ptr++;
1113                   language_team_len--;
1114                 }
1115 
1116               /* Trim trailing blanks.  */
1117               while (language_team_len > 0
1118                      && (language_team_ptr[language_team_len - 1] == ' '
1119                          || language_team_ptr[language_team_len - 1] == '\t'))
1120                 language_team_len--;
1121 
1122               /* Trim last word, if it looks like an URL or email address.  */
1123               {
1124                 size_t i;
1125 
1126                 for (i = language_team_len; i > 0; i--)
1127                   if (language_team_ptr[i - 1] == ' '
1128                       || language_team_ptr[i - 1] == '\t')
1129                     break;
1130                 /* The last word: language_team_ptr[i..language_team_len-1].  */
1131                 if (i < language_team_len
1132                     && (language_team_ptr[i] == '<'
1133                         || language_team_ptr[language_team_len - 1] == '>'
1134                         || memchr (language_team_ptr, '@', language_team_len)
1135                            != NULL
1136                         || memchr (language_team_ptr, '/', language_team_len)
1137                            != NULL))
1138                   {
1139                     /* Trim last word and blanks before it.  */
1140                     while (i > 0
1141                            && (language_team_ptr[i - 1] == ' '
1142                                || language_team_ptr[i - 1] == '\t'))
1143                       i--;
1144                     language_team_len = i;
1145                   }
1146               }
1147 
1148               /* The rest of the Language-Team field should be the english name
1149                  of the languge.  Convert to ISO 639 and ISO 3166 syntax.  */
1150               {
1151                 size_t i;
1152 
1153                 for (i = 0; i < language_variant_table_size; i++)
1154                   if (strlen (language_variant_table[i].english)
1155                       == language_team_len
1156                       && memcmp (language_variant_table[i].english,
1157                                  language_team_ptr, language_team_len) == 0)
1158                     {
1159                       header_fields[LANGUAGE].string =
1160                         language_variant_table[i].code;
1161                       break;
1162                     }
1163               }
1164               if (header_fields[LANGUAGE].string == NULL)
1165                 {
1166                   size_t i;
1167 
1168                   for (i = 0; i < language_table_size; i++)
1169                     if (strlen (language_table[i].english) == language_team_len
1170                         && memcmp (language_table[i].english,
1171                                    language_team_ptr, language_team_len) == 0)
1172                       {
1173                         header_fields[LANGUAGE].string = language_table[i].code;
1174                         break;
1175                       }
1176                 }
1177               if (header_fields[LANGUAGE].string != NULL)
1178                 {
1179                   /* Prepend a space and append a newline.  */
1180                   const char *str = header_fields[LANGUAGE].string;
1181                   size_t len = strlen (str);
1182                   char *copy = (char *) obstack_alloc (&pool, 1 + len + 1 + 1);
1183                   stpcpy (stpcpy (stpcpy (copy, " "), str), "\n");
1184                   header_fields[LANGUAGE].string = copy;
1185                 }
1186               else
1187                 header_fields[LANGUAGE].string = " \n";
1188               header_fields[LANGUAGE].len =
1189                 strlen (header_fields[LANGUAGE].string);
1190             }
1191         }
1192 
1193       {
1194         const char *msgid_bugs_ptr;
1195 
1196         msgid_bugs_ptr = c_strstr (ref->msgstr, "Report-Msgid-Bugs-To:");
1197         if (msgid_bugs_ptr != NULL)
1198           {
1199             size_t msgid_bugs_len;
1200             const char *endp;
1201 
1202             msgid_bugs_ptr += sizeof ("Report-Msgid-Bugs-To:") - 1;
1203 
1204             endp = strchr (msgid_bugs_ptr, '\n');
1205             if (endp == NULL)
1206               {
1207                 /* Add a trailing newline.  */
1208                 char *extended;
1209                 endp = strchr (msgid_bugs_ptr, '\0');
1210                 msgid_bugs_len = (endp - msgid_bugs_ptr) + 1;
1211                 extended = (char *) obstack_alloc (&pool, msgid_bugs_len + 1);
1212                 stpcpy (stpcpy (extended, msgid_bugs_ptr), "\n");
1213                 msgid_bugs_ptr = extended;
1214               }
1215             else
1216               msgid_bugs_len = (endp - msgid_bugs_ptr) + 1;
1217 
1218             header_fields[REPORT_MSGID_BUGS_TO].string = msgid_bugs_ptr;
1219             header_fields[REPORT_MSGID_BUGS_TO].len = msgid_bugs_len;
1220           }
1221       }
1222 
1223       {
1224         const char *pot_date_ptr;
1225 
1226         pot_date_ptr = c_strstr (ref->msgstr, "POT-Creation-Date:");
1227         if (pot_date_ptr != NULL)
1228           {
1229             size_t pot_date_len;
1230             const char *endp;
1231 
1232             pot_date_ptr += sizeof ("POT-Creation-Date:") - 1;
1233 
1234             endp = strchr (pot_date_ptr, '\n');
1235             if (endp == NULL)
1236               {
1237                 /* Add a trailing newline.  */
1238                 char *extended;
1239                 endp = strchr (pot_date_ptr, '\0');
1240                 pot_date_len = (endp - pot_date_ptr) + 1;
1241                 extended = (char *) obstack_alloc (&pool, pot_date_len + 1);
1242                 stpcpy (stpcpy (extended, pot_date_ptr), "\n");
1243                 pot_date_ptr = extended;
1244               }
1245             else
1246               pot_date_len = (endp - pot_date_ptr) + 1;
1247 
1248             header_fields[POT_CREATION_DATE].string = pot_date_ptr;
1249             header_fields[POT_CREATION_DATE].len = pot_date_len;
1250           }
1251       }
1252 
1253       /* Concatenate all the various fields.  */
1254       len = 0;
1255       for (cnt = 0; cnt < UNKNOWN; ++cnt)
1256         if (header_fields[cnt].string != NULL)
1257           len += known_fields[cnt].len + header_fields[cnt].len;
1258       len += header_fields[UNKNOWN].len;
1259 
1260       cp = newp = XNMALLOC (len + 1, char);
1261       newp[len] = '\0';
1262 
1263 #define IF_FILLED(idx)                                                        \
1264       if (header_fields[idx].string)                                          \
1265         newp = stpncpy (stpcpy (newp, known_fields[idx].name),                \
1266                         header_fields[idx].string, header_fields[idx].len)
1267 
1268       IF_FILLED (PROJECT_ID);
1269       IF_FILLED (REPORT_MSGID_BUGS_TO);
1270       IF_FILLED (POT_CREATION_DATE);
1271       IF_FILLED (PO_REVISION_DATE);
1272       IF_FILLED (LAST_TRANSLATOR);
1273       IF_FILLED (LANGUAGE_TEAM);
1274       IF_FILLED (LANGUAGE);
1275       IF_FILLED (MIME_VERSION);
1276       IF_FILLED (CONTENT_TYPE);
1277       IF_FILLED (CONTENT_TRANSFER);
1278       if (header_fields[UNKNOWN].string != NULL)
1279         stpcpy (newp, header_fields[UNKNOWN].string);
1280 
1281 #undef IF_FILLED
1282 
1283       /* Free the temporary memory pool.  */
1284       obstack_free (&pool, NULL);
1285 
1286       msgstr = cp;
1287       msgstr_len = strlen (cp) + 1;
1288 
1289       prev_msgctxt = NULL;
1290       prev_msgid = NULL;
1291       prev_msgid_plural = NULL;
1292     }
1293   else
1294     {
1295       msgstr = def->msgstr;
1296       msgstr_len = def->msgstr_len;
1297 
1298       if (def->is_fuzzy)
1299         {
1300           prev_msgctxt = def->prev_msgctxt;
1301           prev_msgid = def->prev_msgid;
1302           prev_msgid_plural = def->prev_msgid_plural;
1303         }
1304       else
1305         {
1306           prev_msgctxt = def->msgctxt;
1307           prev_msgid = def->msgid;
1308           prev_msgid_plural = def->msgid_plural;
1309         }
1310     }
1311 
1312   result = message_alloc (ref->msgctxt != NULL ? xstrdup (ref->msgctxt) : NULL,
1313                           xstrdup (ref->msgid), ref->msgid_plural,
1314                           msgstr, msgstr_len, &def->pos);
1315 
1316   /* Take the comments from the definition file.  There will be none at
1317      all in the reference file, as it was generated by xgettext.  */
1318   if (def->comment)
1319     for (j = 0; j < def->comment->nitems; ++j)
1320       message_comment_append (result, def->comment->item[j]);
1321 
1322   /* Take the dot comments from the reference file, as they are
1323      generated by xgettext.  Any in the definition file are old ones
1324      collected by previous runs of xgettext and msgmerge.  */
1325   if (ref->comment_dot)
1326     for (j = 0; j < ref->comment_dot->nitems; ++j)
1327       message_comment_dot_append (result, ref->comment_dot->item[j]);
1328 
1329   /* The flags are mixed in a special way.  Some informations come
1330      from the reference message (such as format/no-format), others
1331      come from the definition file (fuzzy or not).  */
1332   result->is_fuzzy = def->is_fuzzy | force_fuzzy;
1333 
1334   /* If ref and def have the same msgid but different msgid_plural, it's
1335      a reason to mark the result fuzzy.  */
1336   if (!result->is_fuzzy
1337       && (ref->msgid_plural != NULL
1338           ? def->msgid_plural == NULL
1339             || strcmp (ref->msgid_plural, def->msgid_plural) != 0
1340           : def->msgid_plural != NULL))
1341     result->is_fuzzy = true;
1342 
1343   for (i = 0; i < NFORMATS; i++)
1344     {
1345       result->is_format[i] = ref->is_format[i];
1346 
1347       /* If the reference message is marked as being a format specifier,
1348          but the definition message is not, we check if the resulting
1349          message would pass "msgfmt -c".  If yes, then all is fine.  If
1350          not, we add a fuzzy marker, because
1351          1. the message needs the translator's attention,
1352          2. msgmerge must not transform a PO file which passes "msgfmt -c"
1353             into a PO file which doesn't.  */
1354       if (!result->is_fuzzy
1355           && possible_format_p (ref->is_format[i])
1356           && !possible_format_p (def->is_format[i])
1357           && check_msgid_msgstr_format_i (ref->msgid, ref->msgid_plural,
1358                                           msgstr, msgstr_len, i, ref->range,
1359                                           distribution, silent_error_logger)
1360              > 0)
1361         result->is_fuzzy = true;
1362     }
1363 
1364   result->range = ref->range;
1365   /* If the definition message was assuming a certain range, but the reference
1366      message does not specify a range any more or specifies a range that is
1367      not the same or a subset, we add a fuzzy marker, because
1368        1. the message needs the translator's attention,
1369        2. msgmerge must not transform a PO file which passes "msgfmt -c"
1370           into a PO file which doesn't.  */
1371   if (!result->is_fuzzy
1372       && has_range_p (def->range)
1373       && !(has_range_p (ref->range)
1374            && ref->range.min >= def->range.min
1375            && ref->range.max <= def->range.max))
1376     result->is_fuzzy = true;
1377 
1378   result->do_wrap = ref->do_wrap;
1379 
1380   for (i = 0; i < NSYNTAXCHECKS; i++)
1381     result->do_syntax_check[i] = ref->do_syntax_check[i];
1382 
1383   /* Insert previous msgid, commented out with "#|".
1384      Do so only when --previous is specified, for backward compatibility.
1385      Since the "previous msgid" represents the original msgid that led to
1386      the current msgstr,
1387        - we can omit it if the resulting message is not fuzzy or is
1388          untranslated (but do this in a later pass, since result->is_fuzzy
1389          is not finalized at this point),
1390        - otherwise, if the corresponding message from the definition file
1391          was translated (not fuzzy), we use that message's msgid,
1392        - otherwise, we use that message's prev_msgid.  */
1393   if (keep_previous)
1394     {
1395       result->prev_msgctxt = prev_msgctxt;
1396       result->prev_msgid = prev_msgid;
1397       result->prev_msgid_plural = prev_msgid_plural;
1398     }
1399 
1400   /* If the reference message was obsolete, make the resulting message
1401      obsolete.  This case doesn't occur for POT files, but users sometimes
1402      use PO files that are themselves the result of msgmerge instead of POT
1403      files.  */
1404   result->obsolete = ref->obsolete;
1405 
1406   /* Take the file position comments from the reference file, as they
1407      are generated by xgettext.  Any in the definition file are old ones
1408      collected by previous runs of xgettext and msgmerge.  */
1409   for (j = 0; j < ref->filepos_count; ++j)
1410     {
1411       lex_pos_ty *pp = &ref->filepos[j];
1412       message_comment_filepos (result, pp->file_name, pp->line_number);
1413     }
1414 
1415   /* Special postprocessing is needed if the reference message is a
1416      plural form and the definition message isn't, or vice versa.  */
1417   if (ref->msgid_plural != NULL)
1418     {
1419       if (def->msgid_plural == NULL)
1420         result->used = 1;
1421     }
1422   else
1423     {
1424       if (def->msgid_plural != NULL)
1425         result->used = 2;
1426     }
1427 
1428   /* All done, return the merged message to the caller.  */
1429   return result;
1430 }
1431 
1432 
1433 #define DOT_FREQUENCY 10
1434 
1435 static void
match_domain(const char * fn1,const char * fn2,definitions_ty * definitions,message_list_ty * refmlp,message_list_ty * resultmlp,struct statistics * stats,unsigned int * processed)1436 match_domain (const char *fn1, const char *fn2,
1437               definitions_ty *definitions, message_list_ty *refmlp,
1438               message_list_ty *resultmlp,
1439               struct statistics *stats, unsigned int *processed)
1440 {
1441   message_ty *header_entry;
1442   unsigned long int nplurals;
1443   const struct expression *plural_expr;
1444   char *untranslated_plural_msgstr;
1445   struct plural_distribution distribution;
1446   struct search_result { message_ty *found; bool fuzzy; } *search_results;
1447   size_t j;
1448 
1449   header_entry =
1450     message_list_search (definitions_current_list (definitions), NULL, "");
1451   extract_plural_expression (header_entry ? header_entry->msgstr : NULL,
1452                              &plural_expr, &nplurals);
1453   untranslated_plural_msgstr = XNMALLOC (nplurals, char);
1454   memset (untranslated_plural_msgstr, '\0', nplurals);
1455 
1456   /* Determine the plural distribution of the plural_expr formula.  */
1457   {
1458     /* Disable error output temporarily.  */
1459     void (*old_po_xerror) (int, const struct message_ty *, const char *, size_t,
1460                            size_t, int, const char *)
1461       = po_xerror;
1462     po_xerror = silent_xerror;
1463 
1464     if (check_plural_eval (plural_expr, nplurals, header_entry,
1465                            &distribution) > 0)
1466       {
1467         distribution.expr = NULL;
1468         distribution.often = NULL;
1469         distribution.often_length = 0;
1470         distribution.histogram = NULL;
1471       }
1472 
1473     po_xerror = old_po_xerror;
1474   }
1475 
1476   /* Most of the time is spent in definitions_search_fuzzy.
1477      Perform it in a separate loop that can be parallelized by an OpenMP
1478      capable compiler.  */
1479   search_results = XNMALLOC (refmlp->nitems, struct search_result);
1480   {
1481     long int nn = refmlp->nitems;
1482     long int jj;
1483 
1484     /* Tell the OpenMP capable compiler to distribute this loop across
1485        several threads.  The schedule is dynamic, because for some messages
1486        the loop body can be executed very quickly, whereas for others it takes
1487        a long time.
1488        Note: The Sun Workshop 6.2 C compiler does not allow a space between
1489        '#' and 'pragma'.  */
1490     #ifdef _OPENMP
1491      #pragma omp parallel for schedule(dynamic)
1492     #endif
1493     for (jj = 0; jj < nn; jj++)
1494       {
1495         message_ty *refmsg = refmlp->item[jj];
1496         message_ty *defmsg;
1497 
1498         /* Because merging can take a while we print something to signal
1499            we are not dead.  */
1500         if (!quiet && verbosity_level <= 1 && *processed % DOT_FREQUENCY == 0)
1501           fputc ('.', stderr);
1502         #ifdef _OPENMP
1503          #pragma omp atomic
1504         #endif
1505         (*processed)++;
1506 
1507         /* See if it is in the other file.  */
1508         defmsg =
1509           definitions_search (definitions, refmsg->msgctxt, refmsg->msgid);
1510         if (defmsg != NULL)
1511           {
1512             search_results[jj].found = defmsg;
1513             search_results[jj].fuzzy = false;
1514           }
1515         else if (!is_header (refmsg)
1516                  /* If the message was not defined at all, try to find a very
1517                     similar message, it could be a typo, or the suggestion may
1518                     help.  */
1519                  && use_fuzzy_matching
1520                  && ((defmsg =
1521                         definitions_search_fuzzy (definitions,
1522                                                   refmsg->msgctxt,
1523                                                   refmsg->msgid)) != NULL))
1524           {
1525             search_results[jj].found = defmsg;
1526             search_results[jj].fuzzy = true;
1527           }
1528         else
1529           search_results[jj].found = NULL;
1530       }
1531   }
1532 
1533   for (j = 0; j < refmlp->nitems; j++)
1534     {
1535       message_ty *refmsg = refmlp->item[j];
1536 
1537       /* See if it is in the other file.
1538          This used definitions_search.  */
1539       if (search_results[j].found != NULL && !search_results[j].fuzzy)
1540         {
1541           message_ty *defmsg = search_results[j].found;
1542           /* Merge the reference with the definition: take the #. and
1543              #: comments from the reference, take the # comments from
1544              the definition, take the msgstr from the definition.  Add
1545              this merged entry to the output message list.  */
1546           message_ty *mp =
1547             message_merge (defmsg, refmsg, false, &distribution);
1548 
1549           /* When producing output for msgfmt, omit messages that are
1550              untranslated or fuzzy (except the header entry).  */
1551           if (!(for_msgfmt
1552                 && (mp->msgstr[0] == '\0' /* untranslated? */
1553                     || (mp->is_fuzzy && !is_header (mp))))) /* fuzzy? */
1554             {
1555               message_list_append (resultmlp, mp);
1556 
1557               /* Remember that this message has been used, when we scan
1558                  later to see if anything was omitted.  */
1559               defmsg->used = 1;
1560             }
1561 
1562           stats->merged++;
1563         }
1564       else if (!is_header (refmsg))
1565         {
1566           /* If the message was not defined at all, try to find a very
1567              similar message, it could be a typo, or the suggestion may
1568              help.  This search assumed use_fuzzy_matching and used
1569              definitions_search_fuzzy.  */
1570           if (search_results[j].found != NULL && search_results[j].fuzzy)
1571             {
1572               message_ty *defmsg = search_results[j].found;
1573               message_ty *mp;
1574 
1575               if (verbosity_level > 1)
1576                 {
1577                   po_gram_error_at_line (&refmsg->pos,
1578                                          _("this message is used but not defined..."));
1579                   error_message_count--;
1580                   po_gram_error_at_line (&defmsg->pos,
1581                                          _("...but this definition is similar"));
1582                 }
1583 
1584               /* Merge the reference with the definition: take the #. and
1585                  #: comments from the reference, take the # comments from
1586                  the definition, take the msgstr from the definition.  Add
1587                  this merged entry to the output message list.  */
1588               mp = message_merge (defmsg, refmsg, true, &distribution);
1589 
1590               message_list_append (resultmlp, mp);
1591 
1592               /* Remember that this message has been used, when we scan
1593                  later to see if anything was omitted.  */
1594               defmsg->used = 1;
1595 
1596               stats->fuzzied++;
1597               if (!quiet && verbosity_level <= 1)
1598                 /* Always print a dot if we handled a fuzzy match.  */
1599                 fputc ('.', stderr);
1600             }
1601           else
1602             {
1603               message_ty *mp;
1604               bool is_untranslated;
1605               const char *p;
1606               const char *pend;
1607 
1608               if (verbosity_level > 1)
1609                 po_gram_error_at_line (&refmsg->pos,
1610                                        _("this message is used but not defined in %s"),
1611                                        fn1);
1612 
1613               mp = message_copy (refmsg);
1614 
1615               /* Test if mp is untranslated.  (It most likely is.)  */
1616               is_untranslated = true;
1617               for (p = mp->msgstr, pend = p + mp->msgstr_len; p < pend; p++)
1618                 if (*p != '\0')
1619                   {
1620                     is_untranslated = false;
1621                     break;
1622                   }
1623 
1624               if (mp->msgid_plural != NULL && is_untranslated)
1625                 {
1626                   /* Change mp->msgstr_len consecutive empty strings into
1627                      nplurals consecutive empty strings.  */
1628                   if (nplurals > mp->msgstr_len)
1629                     mp->msgstr = untranslated_plural_msgstr;
1630                   mp->msgstr_len = nplurals;
1631                 }
1632 
1633               /* When producing output for msgfmt, omit messages that are
1634                  untranslated or fuzzy (except the header entry).  */
1635               if (!(for_msgfmt && (is_untranslated || mp->is_fuzzy)))
1636                 {
1637                   message_list_append (resultmlp, mp);
1638                 }
1639 
1640               stats->missing++;
1641             }
1642         }
1643     }
1644 
1645   free (search_results);
1646 
1647   /* Now postprocess the problematic merges.  This is needed because we
1648      want the result to pass the "msgfmt -c -v" check.  */
1649   {
1650     /* message_merge sets mp->used to 1 or 2, depending on the problem.
1651        Compute the bitwise OR of all these.  */
1652     int problematic = 0;
1653 
1654     for (j = 0; j < resultmlp->nitems; j++)
1655       problematic |= resultmlp->item[j]->used;
1656 
1657     if (problematic)
1658       {
1659         unsigned long int nplurals = 0;
1660 
1661         if (problematic & 1)
1662           {
1663             /* Need to know nplurals of the result domain.  */
1664             message_ty *header_entry =
1665               message_list_search (resultmlp, NULL, "");
1666 
1667             nplurals = get_plural_count (header_entry
1668                                          ? header_entry->msgstr
1669                                          : NULL);
1670           }
1671 
1672         for (j = 0; j < resultmlp->nitems; j++)
1673           {
1674             message_ty *mp = resultmlp->item[j];
1675 
1676             if ((mp->used & 1) && (nplurals > 0))
1677               {
1678                 /* ref->msgid_plural != NULL but def->msgid_plural == NULL.
1679                    Use a copy of def->msgstr for each possible plural form.  */
1680                 size_t new_msgstr_len;
1681                 char *new_msgstr;
1682                 char *p;
1683                 unsigned long i;
1684 
1685                 if (verbosity_level > 1)
1686                   po_gram_error_at_line (&mp->pos,
1687                                          _("this message should define plural forms"));
1688 
1689                 new_msgstr_len = nplurals * mp->msgstr_len;
1690                 new_msgstr = XNMALLOC (new_msgstr_len, char);
1691                 for (i = 0, p = new_msgstr; i < nplurals; i++)
1692                   {
1693                     memcpy (p, mp->msgstr, mp->msgstr_len);
1694                     p += mp->msgstr_len;
1695                   }
1696                 mp->msgstr = new_msgstr;
1697                 mp->msgstr_len = new_msgstr_len;
1698                 mp->is_fuzzy = true;
1699               }
1700 
1701             if ((mp->used & 2) && (mp->msgstr_len > strlen (mp->msgstr) + 1))
1702               {
1703                 /* ref->msgid_plural == NULL but def->msgid_plural != NULL.
1704                    Use only the first among the plural forms.  */
1705 
1706                 if (verbosity_level > 1)
1707                   po_gram_error_at_line (&mp->pos,
1708                                          _("this message should not define plural forms"));
1709 
1710                 mp->msgstr_len = strlen (mp->msgstr) + 1;
1711                 mp->is_fuzzy = true;
1712               }
1713 
1714             /* Postprocessing of this message is done.  */
1715             mp->used = 0;
1716           }
1717       }
1718   }
1719 
1720   /* Now that mp->is_fuzzy is finalized for all messages, remove the
1721      "previous msgid" information from all messages that are not fuzzy or
1722      are untranslated.  */
1723   for (j = 0; j < resultmlp->nitems; j++)
1724     {
1725       message_ty *mp = resultmlp->item[j];
1726 
1727       if (!mp->is_fuzzy || mp->msgstr[0] == '\0')
1728         {
1729           mp->prev_msgctxt = NULL;
1730           mp->prev_msgid = NULL;
1731           mp->prev_msgid_plural = NULL;
1732         }
1733     }
1734 }
1735 
1736 static msgdomain_list_ty *
merge(const char * fn1,const char * fn2,catalog_input_format_ty input_syntax,msgdomain_list_ty ** defp)1737 merge (const char *fn1, const char *fn2, catalog_input_format_ty input_syntax,
1738        msgdomain_list_ty **defp)
1739 {
1740   msgdomain_list_ty *def;
1741   msgdomain_list_ty *ref;
1742   size_t j, k;
1743   unsigned int processed;
1744   struct statistics stats;
1745   msgdomain_list_ty *result;
1746   const char *def_canon_charset;
1747   definitions_ty definitions;
1748   message_list_ty *empty_list;
1749 
1750   stats.merged = stats.fuzzied = stats.missing = stats.obsolete = 0;
1751 
1752   /* This is the definitions file, created by a human.  */
1753   def = read_catalog_file (fn1, input_syntax);
1754 
1755   /* This is the references file, created by groping the sources with
1756      the xgettext program.  */
1757   ref = read_catalog_file (fn2, input_syntax);
1758   /* Add a dummy header entry, if the references file contains none.  */
1759   for (k = 0; k < ref->nitems; k++)
1760     if (message_list_search (ref->item[k]->messages, NULL, "") == NULL)
1761       {
1762         static lex_pos_ty pos = { __FILE__, __LINE__ };
1763         message_ty *refheader = message_alloc (NULL, "", NULL, "", 1, &pos);
1764 
1765         message_list_prepend (ref->item[k]->messages, refheader);
1766       }
1767 
1768   /* The references file can be either in ASCII or in UTF-8.  If it is
1769      in UTF-8, we have to convert the definitions and the compendiums to
1770      UTF-8 as well.  */
1771   {
1772     bool was_utf8 = false;
1773     for (k = 0; k < ref->nitems; k++)
1774       {
1775         message_list_ty *mlp = ref->item[k]->messages;
1776 
1777         for (j = 0; j < mlp->nitems; j++)
1778           if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1779             {
1780               const char *header = mlp->item[j]->msgstr;
1781 
1782               if (header != NULL)
1783                 {
1784                   const char *charsetstr = c_strstr (header, "charset=");
1785 
1786                   if (charsetstr != NULL)
1787                     {
1788                       size_t len;
1789 
1790                       charsetstr += strlen ("charset=");
1791                       len = strcspn (charsetstr, " \t\n");
1792                       if (len == strlen ("UTF-8")
1793                           && c_strncasecmp (charsetstr, "UTF-8", len) == 0)
1794                         was_utf8 = true;
1795                     }
1796                 }
1797             }
1798         }
1799     if (was_utf8)
1800       {
1801         def = iconv_msgdomain_list (def, "UTF-8", true, fn1);
1802         if (compendiums != NULL)
1803           for (k = 0; k < compendiums->nitems; k++)
1804             iconv_message_list (compendiums->item[k], NULL, po_charset_utf8,
1805                                 compendium_filenames->item[k]);
1806       }
1807     else if (compendiums != NULL && compendiums->nitems > 0)
1808       {
1809         /* Ensure that the definitions and the compendiums are in the same
1810            encoding.  Prefer the encoding of the definitions file, if
1811            possible; otherwise, if the definitions file is empty and the
1812            compendiums are all in the same encoding, use that encoding;
1813            otherwise, use UTF-8.  */
1814         bool conversion_done = false;
1815         {
1816           char *charset = NULL;
1817 
1818           /* Get the encoding of the definitions file.  */
1819           for (k = 0; k < def->nitems; k++)
1820             {
1821               message_list_ty *mlp = def->item[k]->messages;
1822 
1823               for (j = 0; j < mlp->nitems; j++)
1824                 if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1825                   {
1826                     const char *header = mlp->item[j]->msgstr;
1827 
1828                     if (header != NULL)
1829                       {
1830                         const char *charsetstr = c_strstr (header, "charset=");
1831 
1832                         if (charsetstr != NULL)
1833                           {
1834                             size_t len;
1835 
1836                             charsetstr += strlen ("charset=");
1837                             len = strcspn (charsetstr, " \t\n");
1838                             charset = (char *) xmalloca (len + 1);
1839                             memcpy (charset, charsetstr, len);
1840                             charset[len] = '\0';
1841                             break;
1842                           }
1843                       }
1844                   }
1845               if (charset != NULL)
1846                 break;
1847             }
1848           if (charset != NULL)
1849             {
1850               const char *canon_charset = po_charset_canonicalize (charset);
1851 
1852               if (canon_charset != NULL)
1853                 {
1854                   bool all_compendiums_iconvable = true;
1855 
1856                   if (compendiums != NULL)
1857                     for (k = 0; k < compendiums->nitems; k++)
1858                       if (!is_message_list_iconvable (compendiums->item[k],
1859                                                       NULL, canon_charset))
1860                         {
1861                           all_compendiums_iconvable = false;
1862                           break;
1863                         }
1864 
1865                   if (all_compendiums_iconvable)
1866                     {
1867                       /* Convert the compendiums to def's encoding.  */
1868                       if (compendiums != NULL)
1869                         for (k = 0; k < compendiums->nitems; k++)
1870                           iconv_message_list (compendiums->item[k],
1871                                               NULL, canon_charset,
1872                                               compendium_filenames->item[k]);
1873                       conversion_done = true;
1874                     }
1875                 }
1876               freea (charset);
1877             }
1878         }
1879         if (!conversion_done)
1880           {
1881             if (def->nitems == 0
1882                 || (def->nitems == 1 && def->item[0]->messages->nitems == 0))
1883               {
1884                 /* The definitions file is empty.
1885                    Compare the encodings of the compendiums.  */
1886                 const char *common_canon_charset = NULL;
1887 
1888                 for (k = 0; k < compendiums->nitems; k++)
1889                   {
1890                     message_list_ty *mlp = compendiums->item[k];
1891                     char *charset = NULL;
1892                     const char *canon_charset = NULL;
1893 
1894                     for (j = 0; j < mlp->nitems; j++)
1895                       if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1896                         {
1897                           const char *header = mlp->item[j]->msgstr;
1898 
1899                           if (header != NULL)
1900                             {
1901                               const char *charsetstr =
1902                                 c_strstr (header, "charset=");
1903 
1904                               if (charsetstr != NULL)
1905                                 {
1906                                   size_t len;
1907 
1908                                   charsetstr += strlen ("charset=");
1909                                   len = strcspn (charsetstr, " \t\n");
1910                                   charset = (char *) xmalloca (len + 1);
1911                                   memcpy (charset, charsetstr, len);
1912                                   charset[len] = '\0';
1913 
1914                                   break;
1915                                 }
1916                             }
1917                         }
1918                     if (charset != NULL)
1919                       {
1920                         canon_charset = po_charset_canonicalize (charset);
1921                         freea (charset);
1922                       }
1923                     /* If no charset declaration was found in this file,
1924                        or if it is not a valid encoding name, or if it
1925                        differs from the common charset found so far,
1926                        we have no common charset.  */
1927                     if (canon_charset == NULL
1928                         || (common_canon_charset != NULL
1929                             && canon_charset != common_canon_charset))
1930                       {
1931                         common_canon_charset = NULL;
1932                         break;
1933                       }
1934                     common_canon_charset = canon_charset;
1935                   }
1936 
1937                 if (common_canon_charset != NULL)
1938                   /* No conversion needed in this case.  */
1939                   conversion_done = true;
1940               }
1941             if (!conversion_done)
1942               {
1943                 /* It's too hairy to find out what would be the optimal target
1944                    encoding.  So, convert everything to UTF-8.  */
1945                 def = iconv_msgdomain_list (def, "UTF-8", true, fn1);
1946                 if (compendiums != NULL)
1947                   for (k = 0; k < compendiums->nitems; k++)
1948                     iconv_message_list (compendiums->item[k],
1949                                         NULL, po_charset_utf8,
1950                                         compendium_filenames->item[k]);
1951               }
1952           }
1953       }
1954   }
1955 
1956   /* Determine canonicalized encoding name of the definitions now, after
1957      conversion.  Only used for fuzzy matching.  */
1958   if (use_fuzzy_matching)
1959     {
1960       def_canon_charset = def->encoding;
1961       if (def_canon_charset == NULL)
1962         {
1963           char *charset = NULL;
1964 
1965           /* Get the encoding of the definitions file.  */
1966           for (k = 0; k < def->nitems; k++)
1967             {
1968               message_list_ty *mlp = def->item[k]->messages;
1969 
1970               for (j = 0; j < mlp->nitems; j++)
1971                 if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1972                   {
1973                     const char *header = mlp->item[j]->msgstr;
1974 
1975                     if (header != NULL)
1976                       {
1977                         const char *charsetstr = c_strstr (header, "charset=");
1978 
1979                         if (charsetstr != NULL)
1980                           {
1981                             size_t len;
1982 
1983                             charsetstr += strlen ("charset=");
1984                             len = strcspn (charsetstr, " \t\n");
1985                             charset = (char *) xmalloca (len + 1);
1986                             memcpy (charset, charsetstr, len);
1987                             charset[len] = '\0';
1988                             break;
1989                           }
1990                       }
1991                   }
1992               if (charset != NULL)
1993                 break;
1994             }
1995           if (charset != NULL)
1996             def_canon_charset = po_charset_canonicalize (charset);
1997           if (def_canon_charset == NULL)
1998             /* Unspecified encoding.  Assume unibyte encoding.  */
1999             def_canon_charset = po_charset_ascii;
2000         }
2001     }
2002   else
2003     def_canon_charset = NULL;
2004 
2005   /* Initialize and preprocess the total set of message definitions.  */
2006   definitions_init (&definitions, def_canon_charset);
2007   empty_list = message_list_alloc (false);
2008 
2009   result = msgdomain_list_alloc (false);
2010   processed = 0;
2011 
2012   /* Every reference must be matched with its definition. */
2013   if (!multi_domain_mode)
2014     for (k = 0; k < ref->nitems; k++)
2015       {
2016         const char *domain = ref->item[k]->domain;
2017         message_list_ty *refmlp = ref->item[k]->messages;
2018         message_list_ty *resultmlp =
2019           msgdomain_list_sublist (result, domain, true);
2020         message_list_ty *defmlp;
2021 
2022         defmlp = msgdomain_list_sublist (def, domain, false);
2023         if (defmlp == NULL)
2024           defmlp = empty_list;
2025         definitions_set_current_list (&definitions, defmlp);
2026 
2027         match_domain (fn1, fn2, &definitions, refmlp, resultmlp,
2028                       &stats, &processed);
2029       }
2030   else
2031     {
2032       /* Apply the references messages in the default domain to each of
2033          the definition domains.  */
2034       message_list_ty *refmlp = ref->item[0]->messages;
2035 
2036       for (k = 0; k < def->nitems; k++)
2037         {
2038           const char *domain = def->item[k]->domain;
2039           message_list_ty *defmlp = def->item[k]->messages;
2040 
2041           /* Ignore the default message domain if it has no messages.  */
2042           if (k > 0 || defmlp->nitems > 0)
2043             {
2044               message_list_ty *resultmlp =
2045                 msgdomain_list_sublist (result, domain, true);
2046 
2047               definitions_set_current_list (&definitions, defmlp);
2048 
2049               match_domain (fn1, fn2, &definitions, refmlp, resultmlp,
2050                             &stats, &processed);
2051             }
2052         }
2053     }
2054 
2055   definitions_destroy (&definitions);
2056 
2057   if (!for_msgfmt)
2058     {
2059       /* Look for messages in the definition file, which are not present
2060          in the reference file, indicating messages which defined but not
2061          used in the program.  Don't scan the compendium(s).  */
2062       for (k = 0; k < def->nitems; ++k)
2063         {
2064           const char *domain = def->item[k]->domain;
2065           message_list_ty *defmlp = def->item[k]->messages;
2066 
2067           for (j = 0; j < defmlp->nitems; j++)
2068             {
2069               message_ty *defmsg = defmlp->item[j];
2070 
2071               if (!defmsg->used)
2072                 {
2073                   /* Remember the old translation although it is not used anymore.
2074                      But we mark it as obsolete.  */
2075                   message_ty *mp;
2076 
2077                   mp = message_copy (defmsg);
2078                   /* Clear the extracted comments.  */
2079                   if (mp->comment_dot != NULL)
2080                     {
2081                       string_list_free (mp->comment_dot);
2082                       mp->comment_dot = NULL;
2083                     }
2084                   /* Clear the file position comments.  */
2085                   if (mp->filepos != NULL)
2086                     {
2087                       size_t i;
2088 
2089                       for (i = 0; i < mp->filepos_count; i++)
2090                         free ((char *) mp->filepos[i].file_name);
2091                       mp->filepos_count = 0;
2092                       free (mp->filepos);
2093                       mp->filepos = NULL;
2094                     }
2095                   /* Mark as obsolete.   */
2096                   mp->obsolete = true;
2097 
2098                   message_list_append (msgdomain_list_sublist (result, domain, true),
2099                                        mp);
2100                   stats.obsolete++;
2101                 }
2102             }
2103         }
2104     }
2105 
2106   /* Determine the known a-priori encoding, if any.  */
2107   if (def->encoding == ref->encoding)
2108     result->encoding = def->encoding;
2109 
2110   /* Report some statistics.  */
2111   if (verbosity_level > 0)
2112     fprintf (stderr, _("%s\
2113 Read %ld old + %ld reference, \
2114 merged %ld, fuzzied %ld, missing %ld, obsolete %ld.\n"),
2115              !quiet && verbosity_level <= 1 ? "\n" : "",
2116              (long) def->nitems, (long) ref->nitems,
2117              (long) stats.merged, (long) stats.fuzzied, (long) stats.missing,
2118              (long) stats.obsolete);
2119   else if (!quiet)
2120     fputs (_(" done.\n"), stderr);
2121 
2122   /* Return results.  */
2123   *defp = def;
2124   return result;
2125 }
2126