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