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.  */
70static int force_po;
71
72/* Output only non-matching messages.  */
73static bool invert_match = false;
74
75/* Selected source files.  */
76static string_list_ty *location_files;
77
78/* Selected domain names.  */
79static string_list_ty *domain_names;
80
81/* Task for each grep pass.  */
82struct 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};
90static struct grep_task grep_task[5];
91
92/* Long options.  */
93static 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.  */
132static void no_pass (int opt)
133#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
134	__attribute__ ((noreturn))
135#endif
136;
137static void usage (int status)
138#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
139	__attribute__ ((noreturn))
140#endif
141;
142static msgdomain_list_ty *process_msgdomain_list (msgdomain_list_ty *mdlp);
143
144
145int
146main (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, _("\
247error 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, _("\
258error 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\
401This is free software; see the source for copying conditions.  There is NO\n\
402warranty; 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
481static void
482no_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.  */
492static void
493usage (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 (_("\
501Usage: %s [OPTION] [INPUTFILE]\n\
502"), program_name);
503      printf ("\n");
504      /* xgettext: no-wrap */
505      printf (_("\
506Extracts all messages of a translation catalog that match a given pattern\n\
507or belong to some given source files.\n\
508"));
509      printf ("\n");
510      printf (_("\
511Mandatory arguments to long options are mandatory for short options too.\n"));
512      printf ("\n");
513      printf (_("\
514Input 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 (_("\
520If no input file is given or if it is -, standard input is read.\n"));
521      printf ("\n");
522      printf (_("\
523Output file location:\n"));
524      printf (_("\
525  -o, --output-file=FILE      write output to specified file\n"));
526      printf (_("\
527The results are written to standard output if no output file is specified\n\
528or if it is -.\n"));
529      printf ("\n");
530      /* xgettext: no-wrap */
531      printf (_("\
532Message 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\
536A message is selected if it comes from one of the specified source files,\n\
537or if it comes from one of the specified domains,\n\
538or if -J is given and its context (msgctxt) matches MSGCTXT-PATTERN,\n\
539or if -K is given and its key (msgid or msgid_plural) matches MSGID-PATTERN,\n\
540or if -T is given and its translation (msgstr) matches MSGSTR-PATTERN,\n\
541or if -C is given and the translator's comment matches COMMENT-PATTERN,\n\
542or if -X is given and the extracted comment matches EXTRACTED-COMMENT-PATTERN.\n\
543\n\
544When more than one selection criterion is specified, the set of selected\n\
545messages is the union of the selected messages of each criterion.\n\
546\n\
547MSGCTXT-PATTERN or MSGID-PATTERN or MSGSTR-PATTERN or COMMENT-PATTERN or\n\
548EXTRACTED-COMMENT-PATTERN syntax:\n\
549  [-E | -F] [-e PATTERN | -f FILE]...\n\
550PATTERNs are basic regular expressions by default, or extended regular\n\
551expressions 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 (_("\
570Input 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 (_("\
577Output 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 (_("\
607Informative 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.  */
623static bool
624filename_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
641static inline int
642nonintr_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.  */
659static bool
660is_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.  */
681static bool
682is_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.  */
795static bool
796is_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
813static void
814process_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
825static msgdomain_list_ty *
826process_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