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