(root)/
gettext-0.22.4/
gettext-tools/
src/
msggrep.c
       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  }