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