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