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