1 /* Extract some translations of a translation catalog.
2    Copyright (C) 2001-2007, 2009-2010, 2012, 2014, 2016, 2018-2020 Free Software
3    Foundation, Inc.
4    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
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 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 #include <alloca.h>
24 
25 #include <assert.h>
26 #include <errno.h>
27 #include <getopt.h>
28 #include <limits.h>
29 #include <locale.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 
34 #include <unistd.h>
35 #if defined _MSC_VER || defined __MINGW32__
36 # include <io.h>
37 #endif
38 
39 #include <fnmatch.h>
40 
41 #include <textstyle.h>
42 
43 #include "noreturn.h"
44 #include "closeout.h"
45 #include "dir-list.h"
46 #include "error.h"
47 #include "error-progname.h"
48 #include "progname.h"
49 #include "relocatable.h"
50 #include "basename-lgpl.h"
51 #include "message.h"
52 #include "read-catalog.h"
53 #include "read-po.h"
54 #include "read-properties.h"
55 #include "read-stringtable.h"
56 #include "write-catalog.h"
57 #include "write-po.h"
58 #include "write-properties.h"
59 #include "write-stringtable.h"
60 #include "str-list.h"
61 #include "msgl-charset.h"
62 #include "xalloc.h"
63 #include "xmalloca.h"
64 #include "libgrep.h"
65 #include "propername.h"
66 #include "gettext.h"
67 
68 #define _(str) gettext (str)
69 
70 
71 /* Force output of PO file even if empty.  */
72 static int force_po;
73 
74 /* Output only non-matching messages.  */
75 static bool invert_match = false;
76 
77 /* Selected source files.  */
78 static string_list_ty *location_files;
79 
80 /* Selected domain names.  */
81 static string_list_ty *domain_names;
82 
83 /* Task for each grep pass.  */
84 struct grep_task {
85   matcher_t *matcher;
86   size_t pattern_count;
87   char *patterns;
88   size_t patterns_size;
89   bool case_insensitive;
90   void *compiled_patterns;
91 };
92 static struct grep_task grep_task[5];
93 
94 /* Long options.  */
95 static const struct option long_options[] =
96 {
97   { "add-location", optional_argument, NULL, 'n' },
98   { "color", optional_argument, NULL, CHAR_MAX + 9 },
99   { "comment", no_argument, NULL, 'C' },
100   { "directory", required_argument, NULL, 'D' },
101   { "domain", required_argument, NULL, 'M' },
102   { "escape", no_argument, NULL, CHAR_MAX + 1 },
103   { "extended-regexp", no_argument, NULL, 'E' },
104   { "extracted-comment", no_argument, NULL, 'X' },
105   { "file", required_argument, NULL, 'f' },
106   { "fixed-strings", no_argument, NULL, 'F' },
107   { "force-po", no_argument, &force_po, 1 },
108   { "help", no_argument, NULL, 'h' },
109   { "ignore-case", no_argument, NULL, 'i' },
110   { "indent", no_argument, NULL, CHAR_MAX + 2 },
111   { "invert-match", no_argument, NULL, 'v' },
112   { "location", required_argument, NULL, 'N' },
113   { "msgctxt", no_argument, NULL, 'J' },
114   { "msgid", no_argument, NULL, 'K' },
115   { "msgstr", no_argument, NULL, 'T' },
116   { "no-escape", no_argument, NULL, CHAR_MAX + 3 },
117   { "no-location", no_argument, NULL, CHAR_MAX + 11 },
118   { "no-wrap", no_argument, NULL, CHAR_MAX + 6 },
119   { "output-file", required_argument, NULL, 'o' },
120   { "properties-input", no_argument, NULL, 'P' },
121   { "properties-output", no_argument, NULL, 'p' },
122   { "regexp", required_argument, NULL, 'e' },
123   { "sort-by-file", no_argument, NULL, CHAR_MAX + 4 },
124   { "sort-output", no_argument, NULL, CHAR_MAX + 5 },
125   { "strict", no_argument, NULL, 'S' },
126   { "stringtable-input", no_argument, NULL, CHAR_MAX + 7 },
127   { "stringtable-output", no_argument, NULL, CHAR_MAX + 8 },
128   { "style", required_argument, NULL, CHAR_MAX + 10 },
129   { "version", no_argument, NULL, 'V' },
130   { "width", required_argument, NULL, 'w' },
131   { NULL, 0, NULL, 0 }
132 };
133 
134 
135 /* Forward declaration of local functions.  */
136 _GL_NORETURN_FUNC static void no_pass (int opt);
137 _GL_NORETURN_FUNC static void usage (int status);
138 static msgdomain_list_ty *process_msgdomain_list (msgdomain_list_ty *mdlp);
139 
140 
141 int
main(int argc,char ** argv)142 main (int argc, char **argv)
143 {
144   int opt;
145   bool do_help;
146   bool do_version;
147   char *output_file;
148   const char *input_file;
149   int grep_pass;
150   msgdomain_list_ty *result;
151   catalog_input_format_ty input_syntax = &input_format_po;
152   catalog_output_format_ty output_syntax = &output_format_po;
153   bool sort_by_filepos = false;
154   bool sort_by_msgid = false;
155   size_t i;
156 
157   /* Set program name for messages.  */
158   set_program_name (argv[0]);
159   error_print_progname = maybe_print_progname;
160 
161   /* Set locale via LC_ALL.  */
162   setlocale (LC_ALL, "");
163 
164   /* Set the text message domain.  */
165   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
166   bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
167   textdomain (PACKAGE);
168 
169   /* Ensure that write errors on stdout are detected.  */
170   atexit (close_stdout);
171 
172   /* Set default values for variables.  */
173   do_help = false;
174   do_version = false;
175   output_file = NULL;
176   input_file = NULL;
177   grep_pass = -1;
178   location_files = string_list_alloc ();
179   domain_names = string_list_alloc ();
180 
181   for (i = 0; i < 5; i++)
182     {
183       struct grep_task *gt = &grep_task[i];
184 
185       gt->matcher = &matcher_grep;
186       gt->pattern_count = 0;
187       gt->patterns = NULL;
188       gt->patterns_size = 0;
189       gt->case_insensitive = false;
190     }
191 
192   while ((opt = getopt_long (argc, argv, "CD:e:Ef:FhiJKM:n:N:o:pPTvVw:X",
193                              long_options, NULL))
194          != EOF)
195     switch (opt)
196       {
197       case '\0':                /* Long option.  */
198         break;
199 
200       case 'C':
201         grep_pass = 3;
202         break;
203 
204       case 'D':
205         dir_list_append (optarg);
206         break;
207 
208       case 'e':
209         if (grep_pass < 0)
210           no_pass (opt);
211         {
212           struct grep_task *gt = &grep_task[grep_pass];
213           /* Append optarg and a newline to gt->patterns.  */
214           size_t len = strlen (optarg);
215           gt->patterns =
216             (char *) xrealloc (gt->patterns, gt->patterns_size + len + 1);
217           memcpy (gt->patterns + gt->patterns_size, optarg, len);
218           gt->patterns_size += len;
219           *(gt->patterns + gt->patterns_size) = '\n';
220           gt->patterns_size += 1;
221           gt->pattern_count++;
222         }
223         break;
224 
225       case 'E':
226         if (grep_pass < 0)
227           no_pass (opt);
228         grep_task[grep_pass].matcher = &matcher_egrep;
229         break;
230 
231       case 'f':
232         if (grep_pass < 0)
233           no_pass (opt);
234         {
235           struct grep_task *gt = &grep_task[grep_pass];
236           /* Append the contents of the specified file to gt->patterns.  */
237           FILE *fp = fopen (optarg, "r");
238 
239           if (fp == NULL)
240             error (EXIT_FAILURE, errno,
241                    _("error while opening \"%s\" for reading"), optarg);
242 
243           while (!feof (fp))
244             {
245               char buf[4096];
246               size_t count = fread (buf, 1, sizeof buf, fp);
247 
248               if (count == 0)
249                 {
250                   if (ferror (fp))
251                     error (EXIT_FAILURE, errno,
252                            _("error while reading \"%s\""), optarg);
253                   /* EOF reached.  */
254                   break;
255                 }
256 
257               gt->patterns =
258                 (char *) xrealloc (gt->patterns, gt->patterns_size + count);
259               memcpy (gt->patterns + gt->patterns_size, buf, count);
260               gt->patterns_size += count;
261             }
262 
263           /* Append a final newline if file ended in a non-newline.  */
264           if (gt->patterns_size > 0
265               && *(gt->patterns + gt->patterns_size - 1) != '\n')
266             {
267               gt->patterns =
268                 (char *) xrealloc (gt->patterns, gt->patterns_size + 1);
269               *(gt->patterns + gt->patterns_size) = '\n';
270               gt->patterns_size += 1;
271             }
272 
273           fclose (fp);
274           gt->pattern_count++;
275         }
276         break;
277 
278       case 'F':
279         if (grep_pass < 0)
280           no_pass (opt);
281         grep_task[grep_pass].matcher = &matcher_fgrep;
282         break;
283 
284       case 'h':
285         do_help = true;
286         break;
287 
288       case 'i':
289         if (grep_pass < 0)
290           no_pass (opt);
291         grep_task[grep_pass].case_insensitive = true;
292         break;
293 
294       case 'J':
295         grep_pass = 0;
296         break;
297 
298       case 'K':
299         grep_pass = 1;
300         break;
301 
302       case 'M':
303         string_list_append (domain_names, optarg);
304         break;
305 
306       case 'n':
307         if (handle_filepos_comment_option (optarg))
308           usage (EXIT_FAILURE);
309         break;
310 
311       case 'N':
312         string_list_append (location_files, optarg);
313         break;
314 
315       case 'o':
316         output_file = optarg;
317         break;
318 
319       case 'p':
320         output_syntax = &output_format_properties;
321         break;
322 
323       case 'P':
324         input_syntax = &input_format_properties;
325         break;
326 
327       case 'S':
328         message_print_style_uniforum ();
329         break;
330 
331       case 'T':
332         grep_pass = 2;
333         break;
334 
335       case 'v':
336         invert_match = true;
337         break;
338 
339       case 'V':
340         do_version = true;
341         break;
342 
343       case 'w':
344         {
345           int value;
346           char *endp;
347           value = strtol (optarg, &endp, 10);
348           if (endp != optarg)
349             message_page_width_set (value);
350         }
351         break;
352 
353       case 'X':
354         grep_pass = 4;
355         break;
356 
357       case CHAR_MAX + 1:
358         message_print_style_escape (true);
359         break;
360 
361       case CHAR_MAX + 2:
362         message_print_style_indent ();
363         break;
364 
365       case CHAR_MAX + 3:
366         message_print_style_escape (false);
367         break;
368 
369       case CHAR_MAX + 4:
370         sort_by_filepos = true;
371         break;
372 
373       case CHAR_MAX + 5:
374         sort_by_msgid = true;
375         break;
376 
377       case CHAR_MAX + 6: /* --no-wrap */
378         message_page_width_ignore ();
379         break;
380 
381       case CHAR_MAX + 7: /* --stringtable-input */
382         input_syntax = &input_format_stringtable;
383         break;
384 
385       case CHAR_MAX + 8: /* --stringtable-output */
386         output_syntax = &output_format_stringtable;
387         break;
388 
389       case CHAR_MAX + 9: /* --color */
390         if (handle_color_option (optarg) || color_test_mode)
391           usage (EXIT_FAILURE);
392         break;
393 
394       case CHAR_MAX + 10: /* --style */
395         handle_style_option (optarg);
396         break;
397 
398       case CHAR_MAX + 11: /* --no-location */
399         message_print_style_filepos (filepos_comment_none);
400         break;
401 
402       default:
403         usage (EXIT_FAILURE);
404         break;
405       }
406 
407   /* Version information is requested.  */
408   if (do_version)
409     {
410       printf ("%s (GNU %s) %s\n", last_component (program_name),
411               PACKAGE, VERSION);
412       /* xgettext: no-wrap */
413       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
414 License GPLv3+: GNU GPL version 3 or later <%s>\n\
415 This is free software: you are free to change and redistribute it.\n\
416 There is NO WARRANTY, to the extent permitted by law.\n\
417 "),
418               "2001-2020", "https://gnu.org/licenses/gpl.html");
419       printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
420       exit (EXIT_SUCCESS);
421     }
422 
423   /* Help is requested.  */
424   if (do_help)
425     usage (EXIT_SUCCESS);
426 
427   /* Test whether we have an .po file name as argument.  */
428   if (optind == argc)
429     input_file = "-";
430   else if (optind + 1 == argc)
431     input_file = argv[optind];
432   else
433     {
434       error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
435       usage (EXIT_FAILURE);
436     }
437 
438   /* Verify selected options.  */
439   if (sort_by_msgid && sort_by_filepos)
440     error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
441            "--sort-output", "--sort-by-file");
442 
443   /* Compile the patterns.  */
444   for (grep_pass = 0; grep_pass < 5; grep_pass++)
445     {
446       struct grep_task *gt = &grep_task[grep_pass];
447 
448       if (gt->pattern_count > 0)
449         {
450           if (gt->patterns_size > 0)
451             {
452               /* Strip trailing newline.  */
453               assert (gt->patterns[gt->patterns_size - 1] == '\n');
454               gt->patterns_size--;
455             }
456           gt->compiled_patterns =
457             gt->matcher->compile (gt->patterns, gt->patterns_size,
458                                   gt->case_insensitive, false, false, '\n');
459         }
460     }
461 
462   /* Read input file.  */
463   result = read_catalog_file (input_file, input_syntax);
464 
465   if (grep_task[0].pattern_count > 0
466       || grep_task[1].pattern_count > 0
467       || grep_task[2].pattern_count > 0
468       || grep_task[3].pattern_count > 0
469       || grep_task[4].pattern_count > 0)
470     {
471       /* Warn if the current locale is not suitable for this PO file.  */
472       compare_po_locale_charsets (result);
473     }
474 
475   /* Select the messages.  */
476   result = process_msgdomain_list (result);
477 
478   /* Sort the results.  */
479   if (sort_by_filepos)
480     msgdomain_list_sort_by_filepos (result);
481   else if (sort_by_msgid)
482     msgdomain_list_sort_by_msgid (result);
483 
484   /* Write the merged message list out.  */
485   msgdomain_list_print (result, output_file, output_syntax, force_po, false);
486 
487   exit (EXIT_SUCCESS);
488 }
489 
490 
491 static void
no_pass(int opt)492 no_pass (int opt)
493 {
494   error (EXIT_SUCCESS, 0,
495          _("option '%c' cannot be used before 'J' or 'K' or 'T' or 'C' or 'X' has been specified"),
496          opt);
497   usage (EXIT_FAILURE);
498 }
499 
500 
501 /* Display usage information and exit.  */
502 static void
usage(int status)503 usage (int status)
504 {
505   if (status != EXIT_SUCCESS)
506     fprintf (stderr, _("Try '%s --help' for more information.\n"),
507              program_name);
508   else
509     {
510       printf (_("\
511 Usage: %s [OPTION] [INPUTFILE]\n\
512 "), program_name);
513       printf ("\n");
514       /* xgettext: no-wrap */
515       printf (_("\
516 Extracts all messages of a translation catalog that match a given pattern\n\
517 or belong to some given source files.\n\
518 "));
519       printf ("\n");
520       printf (_("\
521 Mandatory arguments to long options are mandatory for short options too.\n"));
522       printf ("\n");
523       printf (_("\
524 Input file location:\n"));
525       printf (_("\
526   INPUTFILE                   input PO file\n"));
527       printf (_("\
528   -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
529       printf (_("\
530 If no input file is given or if it is -, standard input is read.\n"));
531       printf ("\n");
532       printf (_("\
533 Output file location:\n"));
534       printf (_("\
535   -o, --output-file=FILE      write output to specified file\n"));
536       printf (_("\
537 The results are written to standard output if no output file is specified\n\
538 or if it is -.\n"));
539       printf ("\n");
540       /* xgettext: no-wrap */
541       printf (_("\
542 Message selection:\n\
543   [-N SOURCEFILE]... [-M DOMAINNAME]...\n\
544   [-J MSGCTXT-PATTERN] [-K MSGID-PATTERN] [-T MSGSTR-PATTERN]\n\
545   [-C COMMENT-PATTERN] [-X EXTRACTED-COMMENT-PATTERN]\n\
546 A message is selected if it comes from one of the specified source files,\n\
547 or if it comes from one of the specified domains,\n\
548 or if -J is given and its context (msgctxt) matches MSGCTXT-PATTERN,\n\
549 or if -K is given and its key (msgid or msgid_plural) matches MSGID-PATTERN,\n\
550 or if -T is given and its translation (msgstr) matches MSGSTR-PATTERN,\n\
551 or if -C is given and the translator's comment matches COMMENT-PATTERN,\n\
552 or if -X is given and the extracted comment matches EXTRACTED-COMMENT-PATTERN.\n\
553 \n\
554 When more than one selection criterion is specified, the set of selected\n\
555 messages is the union of the selected messages of each criterion.\n\
556 \n\
557 MSGCTXT-PATTERN or MSGID-PATTERN or MSGSTR-PATTERN or COMMENT-PATTERN or\n\
558 EXTRACTED-COMMENT-PATTERN syntax:\n\
559   [-E | -F] [-e PATTERN | -f FILE]...\n\
560 PATTERNs are basic regular expressions by default, or extended regular\n\
561 expressions if -E is given, or fixed strings if -F is given.\n\
562 \n\
563   -N, --location=SOURCEFILE   select messages extracted from SOURCEFILE\n\
564   -M, --domain=DOMAINNAME     select messages belonging to domain DOMAINNAME\n\
565   -J, --msgctxt               start of patterns for the msgctxt\n\
566   -K, --msgid                 start of patterns for the msgid\n\
567   -T, --msgstr                start of patterns for the msgstr\n\
568   -C, --comment               start of patterns for the translator's comment\n\
569   -X, --extracted-comment     start of patterns for the extracted comment\n\
570   -E, --extended-regexp       PATTERN is an extended regular expression\n\
571   -F, --fixed-strings         PATTERN is a set of newline-separated strings\n\
572   -e, --regexp=PATTERN        use PATTERN as a regular expression\n\
573   -f, --file=FILE             obtain PATTERN from FILE\n\
574   -i, --ignore-case           ignore case distinctions\n\
575   -v, --invert-match          output only the messages that do not match any\n\
576                               selection criterion\n\
577 "));
578       printf ("\n");
579       printf (_("\
580 Input file syntax:\n"));
581       printf (_("\
582   -P, --properties-input      input file is in Java .properties syntax\n"));
583       printf (_("\
584       --stringtable-input     input file is in NeXTstep/GNUstep .strings syntax\n"));
585       printf ("\n");
586       printf (_("\
587 Output details:\n"));
588       printf (_("\
589       --color                 use colors and other text attributes always\n\
590       --color=WHEN            use colors and other text attributes if WHEN.\n\
591                               WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
592       printf (_("\
593       --style=STYLEFILE       specify CSS style rule file for --color\n"));
594       printf (_("\
595       --no-escape             do not use C escapes in output (default)\n"));
596       printf (_("\
597       --escape                use C escapes in output, no extended chars\n"));
598       printf (_("\
599       --force-po              write PO file even if empty\n"));
600       printf (_("\
601       --indent                indented output style\n"));
602       printf (_("\
603       --no-location           suppress '#: filename:line' lines\n"));
604       printf (_("\
605   -n, --add-location          preserve '#: filename:line' lines (default)\n"));
606       printf (_("\
607       --strict                strict Uniforum output style\n"));
608       printf (_("\
609   -p, --properties-output     write out a Java .properties file\n"));
610       printf (_("\
611       --stringtable-output    write out a NeXTstep/GNUstep .strings file\n"));
612       printf (_("\
613   -w, --width=NUMBER          set output page width\n"));
614       printf (_("\
615       --no-wrap               do not break long message lines, longer than\n\
616                               the output page width, into several lines\n"));
617       printf (_("\
618       --sort-output           generate sorted output\n"));
619       printf (_("\
620       --sort-by-file          sort output by file location\n"));
621       printf ("\n");
622       printf (_("\
623 Informative output:\n"));
624       printf (_("\
625   -h, --help                  display this help and exit\n"));
626       printf (_("\
627   -V, --version               output version information and exit\n"));
628       printf ("\n");
629       /* TRANSLATORS: The first placeholder is the web address of the Savannah
630          project of this package.  The second placeholder is the bug-reporting
631          email address for this package.  Please add _another line_ saying
632          "Report translation bugs to <...>\n" with the address for translation
633          bugs (typically your translation team's web or email address).  */
634       printf(_("\
635 Report bugs in the bug tracker at <%s>\n\
636 or by email to <%s>.\n"),
637              "https://savannah.gnu.org/projects/gettext",
638              "bug-gettext@gnu.org");
639     }
640 
641   exit (status);
642 }
643 
644 
645 /* Return 1 if FILENAME is contained in a list of filename patterns,
646    0 otherwise.  */
647 static bool
filename_list_match(const string_list_ty * slp,const char * filename)648 filename_list_match (const string_list_ty *slp, const char *filename)
649 {
650   size_t j;
651 
652   for (j = 0; j < slp->nitems; ++j)
653     if (fnmatch (slp->item[j], filename, FNM_PATHNAME) == 0)
654       return true;
655   return false;
656 }
657 
658 
659 #ifdef EINTR
660 
661 /* EINTR handling for close().
662    These functions can return -1/EINTR even though we don't have any
663    signal handlers set up, namely when we get interrupted via SIGSTOP.  */
664 
665 static inline int
nonintr_close(int fd)666 nonintr_close (int fd)
667 {
668   int retval;
669 
670   do
671     retval = close (fd);
672   while (retval < 0 && errno == EINTR);
673 
674   return retval;
675 }
676 #undef close
677 #define close nonintr_close
678 
679 #endif
680 
681 
682 /* Process a string STR of size LEN bytes through grep, and return true
683    if it matches.  */
684 static bool
is_string_selected(int grep_pass,const char * str,size_t len)685 is_string_selected (int grep_pass, const char *str, size_t len)
686 {
687   const struct grep_task *gt = &grep_task[grep_pass];
688 
689   if (gt->pattern_count > 0)
690     {
691       size_t match_size;
692       size_t match_offset;
693 
694       match_offset =
695         gt->matcher->execute (gt->compiled_patterns, str, len,
696                               &match_size, false);
697       return (match_offset != (size_t) -1);
698     }
699   else
700     return 0;
701 }
702 
703 
704 /* Return true if a message matches, considering only the positive selection
705    criteria and ignoring --invert-match.  */
706 static bool
is_message_selected_no_invert(const message_ty * mp)707 is_message_selected_no_invert (const message_ty *mp)
708 {
709   size_t i;
710   const char *msgstr;
711   size_t msgstr_len;
712   const char *p;
713 
714   /* Test whether one of mp->filepos[] is selected.  */
715   for (i = 0; i < mp->filepos_count; i++)
716     if (filename_list_match (location_files, mp->filepos[i].file_name))
717       return true;
718 
719   /* Test msgctxt using the --msgctxt arguments.  */
720   if (mp->msgctxt != NULL
721       && is_string_selected (0, mp->msgctxt, strlen (mp->msgctxt)))
722     return true;
723 
724   /* Test msgid and msgid_plural using the --msgid arguments.  */
725   if (is_string_selected (1, mp->msgid, strlen (mp->msgid)))
726     return true;
727   if (mp->msgid_plural != NULL
728       && is_string_selected (1, mp->msgid_plural, strlen (mp->msgid_plural)))
729     return true;
730 
731   /* Test msgstr using the --msgstr arguments.  */
732   msgstr = mp->msgstr;
733   msgstr_len = mp->msgstr_len;
734   /* Process each NUL delimited substring separately.  */
735   for (p = msgstr; p < msgstr + msgstr_len; )
736     {
737       size_t length = strlen (p);
738 
739       if (is_string_selected (2, p, length))
740         return true;
741 
742       p += length + 1;
743     }
744 
745   /* Test translator comments using the --comment arguments.  */
746   if (grep_task[3].pattern_count > 0
747       && mp->comment != NULL && mp->comment->nitems > 0)
748     {
749       size_t length;
750       char *total_comment;
751       char *q;
752       size_t j;
753       bool selected;
754 
755       length = 0;
756       for (j = 0; j < mp->comment->nitems; j++)
757         length += strlen (mp->comment->item[j]) + 1;
758       total_comment = (char *) xmalloca (length);
759 
760       q = total_comment;
761       for (j = 0; j < mp->comment->nitems; j++)
762         {
763           size_t l = strlen (mp->comment->item[j]);
764 
765           memcpy (q, mp->comment->item[j], l);
766           q += l;
767           *q++ = '\n';
768         }
769       if (q != total_comment + length)
770         abort ();
771 
772       selected = is_string_selected (3, total_comment, length);
773 
774       freea (total_comment);
775 
776       if (selected)
777         return true;
778     }
779 
780   /* Test extracted comments using the --extracted-comment arguments.  */
781   if (grep_task[4].pattern_count > 0
782       && mp->comment_dot != NULL && mp->comment_dot->nitems > 0)
783     {
784       size_t length;
785       char *total_comment;
786       char *q;
787       size_t j;
788       bool selected;
789 
790       length = 0;
791       for (j = 0; j < mp->comment_dot->nitems; j++)
792         length += strlen (mp->comment_dot->item[j]) + 1;
793       total_comment = (char *) xmalloca (length);
794 
795       q = total_comment;
796       for (j = 0; j < mp->comment_dot->nitems; j++)
797         {
798           size_t l = strlen (mp->comment_dot->item[j]);
799 
800           memcpy (q, mp->comment_dot->item[j], l);
801           q += l;
802           *q++ = '\n';
803         }
804       if (q != total_comment + length)
805         abort ();
806 
807       selected = is_string_selected (4, total_comment, length);
808 
809       freea (total_comment);
810 
811       if (selected)
812         return true;
813     }
814 
815   return false;
816 }
817 
818 
819 /* Return true if a message matches.  */
820 static bool
is_message_selected(const message_ty * mp)821 is_message_selected (const message_ty *mp)
822 {
823   bool result;
824 
825   /* Always keep the header entry.  */
826   if (is_header (mp))
827     return true;
828 
829   result = is_message_selected_no_invert (mp);
830 
831   if (invert_match)
832     return !result;
833   else
834     return result;
835 }
836 
837 
838 static void
process_message_list(const char * domain,message_list_ty * mlp)839 process_message_list (const char *domain, message_list_ty *mlp)
840 {
841   if (string_list_member (domain_names, domain))
842     /* Keep all the messages in the list.  */
843     ;
844   else
845     /* Keep only the selected messages.  */
846     message_list_remove_if_not (mlp, is_message_selected);
847 }
848 
849 
850 static msgdomain_list_ty *
process_msgdomain_list(msgdomain_list_ty * mdlp)851 process_msgdomain_list (msgdomain_list_ty *mdlp)
852 {
853   size_t k;
854 
855   for (k = 0; k < mdlp->nitems; k++)
856     process_message_list (mdlp->item[k]->domain, mdlp->item[k]->messages);
857 
858   return mdlp;
859 }
860