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