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