(root)/
gettext-0.22.4/
gettext-tools/
src/
msgfilter.c
       1  /* Edit translations using a subprocess.
       2     Copyright (C) 2001-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  
      23  #include <getopt.h>
      24  #include <limits.h>
      25  #include <locale.h>
      26  #include <stdio.h>
      27  #include <stdlib.h>
      28  #include <string.h>
      29  #include <sys/types.h>
      30  #include <sys/time.h>
      31  #include <unistd.h>
      32  
      33  #include <textstyle.h>
      34  
      35  #include "noreturn.h"
      36  #include "closeout.h"
      37  #include "dir-list.h"
      38  #include "error.h"
      39  #include "xvasprintf.h"
      40  #include "error-progname.h"
      41  #include "progname.h"
      42  #include "relocatable.h"
      43  #include "basename-lgpl.h"
      44  #include "message.h"
      45  #include "read-catalog.h"
      46  #include "read-po.h"
      47  #include "read-properties.h"
      48  #include "read-stringtable.h"
      49  #include "write-catalog.h"
      50  #include "write-po.h"
      51  #include "write-properties.h"
      52  #include "write-stringtable.h"
      53  #include "msgl-charset.h"
      54  #include "xalloc.h"
      55  #include "findprog.h"
      56  #include "pipe-filter.h"
      57  #include "xsetenv.h"
      58  #include "filters.h"
      59  #include "msgl-iconv.h"
      60  #include "po-charset.h"
      61  #include "propername.h"
      62  #include "gettext.h"
      63  
      64  #define _(str) gettext (str)
      65  
      66  
      67  /* We use a child process, and communicate through a bidirectional pipe.  */
      68  
      69  
      70  /* Force output of PO file even if empty.  */
      71  static int force_po;
      72  
      73  /* Keep the header entry unmodified.  */
      74  static int keep_header;
      75  
      76  /* Name of the subprogram.  */
      77  static const char *sub_name;
      78  
      79  /* Pathname of the subprogram.  */
      80  static const char *sub_path;
      81  
      82  /* Argument list for the subprogram.  */
      83  static const char **sub_argv;
      84  static int sub_argc;
      85  
      86  static bool newline;
      87  
      88  /* Filter function.  */
      89  static void (*filter) (const char *str, size_t len, char **resultp, size_t *lengthp);
      90  
      91  /* Long options.  */
      92  static const struct option long_options[] =
      93  {
      94    { "add-location", optional_argument, NULL, 'n' },
      95    { "color", optional_argument, NULL, CHAR_MAX + 6 },
      96    { "directory", required_argument, NULL, 'D' },
      97    { "escape", no_argument, NULL, 'E' },
      98    { "force-po", no_argument, &force_po, 1 },
      99    { "help", no_argument, NULL, 'h' },
     100    { "indent", no_argument, NULL, CHAR_MAX + 1 },
     101    { "input", required_argument, NULL, 'i' },
     102    { "keep-header", no_argument, &keep_header, 1 },
     103    { "newline", no_argument, NULL, CHAR_MAX + 9 },
     104    { "no-escape", no_argument, NULL, CHAR_MAX + 2 },
     105    { "no-location", no_argument, NULL, CHAR_MAX + 8 },
     106    { "no-wrap", no_argument, NULL, CHAR_MAX + 3 },
     107    { "output-file", required_argument, NULL, 'o' },
     108    { "properties-input", no_argument, NULL, 'P' },
     109    { "properties-output", no_argument, NULL, 'p' },
     110    { "sort-by-file", no_argument, NULL, 'F' },
     111    { "sort-output", no_argument, NULL, 's' },
     112    { "strict", no_argument, NULL, 'S' },
     113    { "stringtable-input", no_argument, NULL, CHAR_MAX + 4 },
     114    { "stringtable-output", no_argument, NULL, CHAR_MAX + 5 },
     115    { "style", required_argument, NULL, CHAR_MAX + 7 },
     116    { "version", no_argument, NULL, 'V' },
     117    { "width", required_argument, NULL, 'w' },
     118    { NULL, 0, NULL, 0 }
     119  };
     120  
     121  
     122  /* Forward declaration of local functions.  */
     123  _GL_NORETURN_FUNC static void usage (int status);
     124  static void generic_filter (const char *str, size_t len, char **resultp, size_t *lengthp);
     125  static msgdomain_list_ty *process_msgdomain_list (msgdomain_list_ty *mdlp);
     126  
     127  
     128  int
     129  main (int argc, char **argv)
     130  {
     131    int opt;
     132    bool do_help;
     133    bool do_version;
     134    char *output_file;
     135    const char *input_file;
     136    msgdomain_list_ty *result;
     137    catalog_input_format_ty input_syntax = &input_format_po;
     138    catalog_output_format_ty output_syntax = &output_format_po;
     139    bool sort_by_filepos = false;
     140    bool sort_by_msgid = false;
     141    int i;
     142  
     143    /* Set program name for messages.  */
     144    set_program_name (argv[0]);
     145    error_print_progname = maybe_print_progname;
     146  
     147    /* Set locale via LC_ALL.  */
     148    setlocale (LC_ALL, "");
     149  
     150    /* Set the text message domain.  */
     151    bindtextdomain (PACKAGE, relocate (LOCALEDIR));
     152    bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
     153    textdomain (PACKAGE);
     154  
     155    /* Ensure that write errors on stdout are detected.  */
     156    atexit (close_stdout);
     157  
     158    /* Set default values for variables.  */
     159    do_help = false;
     160    do_version = false;
     161    output_file = NULL;
     162    input_file = NULL;
     163  
     164    /* The '+' in the options string causes option parsing to terminate when
     165       the first non-option, i.e. the subprogram name, is encountered.  */
     166    while ((opt = getopt_long (argc, argv, "+D:EFhi:n:o:pPsVw:", long_options,
     167                               NULL))
     168           != EOF)
     169      switch (opt)
     170        {
     171        case '\0':                /* Long option.  */
     172          break;
     173  
     174        case 'D':
     175          dir_list_append (optarg);
     176          break;
     177  
     178        case 'E':
     179          message_print_style_escape (true);
     180          break;
     181  
     182        case 'F':
     183          sort_by_filepos = true;
     184          break;
     185  
     186        case 'h':
     187          do_help = true;
     188          break;
     189  
     190        case 'i':
     191          if (input_file != NULL)
     192            {
     193              error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
     194              usage (EXIT_FAILURE);
     195            }
     196          input_file = optarg;
     197          break;
     198  
     199        case 'n':
     200          if (handle_filepos_comment_option (optarg))
     201            usage (EXIT_FAILURE);
     202          break;
     203  
     204        case 'o':
     205          output_file = optarg;
     206          break;
     207  
     208        case 'p':
     209          output_syntax = &output_format_properties;
     210          break;
     211  
     212        case 'P':
     213          input_syntax = &input_format_properties;
     214          break;
     215  
     216        case 's':
     217          sort_by_msgid = true;
     218          break;
     219  
     220        case 'S':
     221          message_print_style_uniforum ();
     222          break;
     223  
     224        case 'V':
     225          do_version = true;
     226          break;
     227  
     228        case 'w':
     229          {
     230            int value;
     231            char *endp;
     232            value = strtol (optarg, &endp, 10);
     233            if (endp != optarg)
     234              message_page_width_set (value);
     235          }
     236          break;
     237  
     238        case CHAR_MAX + 1:
     239          message_print_style_indent ();
     240          break;
     241  
     242        case CHAR_MAX + 2:
     243          message_print_style_escape (false);
     244          break;
     245  
     246        case CHAR_MAX + 3: /* --no-wrap */
     247          message_page_width_ignore ();
     248          break;
     249  
     250        case CHAR_MAX + 4: /* --stringtable-input */
     251          input_syntax = &input_format_stringtable;
     252          break;
     253  
     254        case CHAR_MAX + 5: /* --stringtable-output */
     255          output_syntax = &output_format_stringtable;
     256          break;
     257  
     258        case CHAR_MAX + 6: /* --color */
     259          if (handle_color_option (optarg) || color_test_mode)
     260            usage (EXIT_FAILURE);
     261          break;
     262  
     263        case CHAR_MAX + 7: /* --style */
     264          handle_style_option (optarg);
     265          break;
     266  
     267        case CHAR_MAX + 8: /* --no-location */
     268          message_print_style_filepos (filepos_comment_none);
     269          break;
     270  
     271        case CHAR_MAX + 9: /* --newline */
     272          newline = true;
     273          break;
     274  
     275        default:
     276          usage (EXIT_FAILURE);
     277          break;
     278        }
     279  
     280    /* Version information is requested.  */
     281    if (do_version)
     282      {
     283        printf ("%s (GNU %s) %s\n", last_component (program_name),
     284                PACKAGE, VERSION);
     285        /* xgettext: no-wrap */
     286        printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
     287  License GPLv3+: GNU GPL version 3 or later <%s>\n\
     288  This is free software: you are free to change and redistribute it.\n\
     289  There is NO WARRANTY, to the extent permitted by law.\n\
     290  "),
     291                "2001-2023", "https://gnu.org/licenses/gpl.html");
     292        printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
     293        exit (EXIT_SUCCESS);
     294      }
     295  
     296    /* Help is requested.  */
     297    if (do_help)
     298      usage (EXIT_SUCCESS);
     299  
     300    /* Test for the subprogram name.  */
     301    if (optind == argc)
     302      error (EXIT_FAILURE, 0, _("missing filter name"));
     303    sub_name = argv[optind];
     304  
     305    /* Verify selected options.  */
     306    if (sort_by_msgid && sort_by_filepos)
     307      error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
     308             "--sort-output", "--sort-by-file");
     309  
     310    /* Build argument list for the program.  */
     311    sub_argc = argc - optind;
     312    sub_argv = XNMALLOC (sub_argc + 1, const char *);
     313    for (i = 0; i < sub_argc; i++)
     314      sub_argv[i] = argv[optind + i];
     315    sub_argv[i] = NULL;
     316  
     317    /* Extra checks for sed scripts.  */
     318    if (strcmp (sub_name, "sed") == 0)
     319      {
     320        if (sub_argc == 1)
     321          error (EXIT_FAILURE, 0,
     322                 _("at least one sed script must be specified"));
     323  
     324        /* Replace GNU sed specific options with portable sed options.  */
     325        for (i = 1; i < sub_argc; i++)
     326          {
     327            if (strcmp (sub_argv[i], "--expression") == 0)
     328              sub_argv[i] = "-e";
     329            else if (strcmp (sub_argv[i], "--file") == 0)
     330              sub_argv[i] = "-f";
     331            else if (strcmp (sub_argv[i], "--quiet") == 0
     332                     || strcmp (sub_argv[i], "--silent") == 0)
     333              sub_argv[i] = "-n";
     334  
     335            if (strcmp (sub_argv[i], "-e") == 0
     336                || strcmp (sub_argv[i], "-f") == 0)
     337              i++;
     338          }
     339      }
     340  
     341    /* By default, input comes from standard input.  */
     342    if (input_file == NULL)
     343      input_file = "-";
     344  
     345    /* Read input file.  */
     346    result = read_catalog_file (input_file, input_syntax);
     347  
     348    /* Recognize special programs as built-ins.  */
     349    if (strcmp (sub_name, "recode-sr-latin") == 0 && sub_argc == 1)
     350      {
     351        filter = serbian_to_latin;
     352  
     353        /* Convert the input to UTF-8 first.  */
     354        result = iconv_msgdomain_list (result, po_charset_utf8, true, input_file);
     355      }
     356    else if (strcmp (sub_name, "quot") == 0 && sub_argc == 1)
     357      {
     358        filter = ascii_quote_to_unicode;
     359  
     360        /* Convert the input to UTF-8 first.  */
     361        result = iconv_msgdomain_list (result, po_charset_utf8, true, input_file);
     362      }
     363    else if (strcmp (sub_name, "boldquot") == 0 && sub_argc == 1)
     364      {
     365        filter = ascii_quote_to_unicode_bold;
     366  
     367        /* Convert the input to UTF-8 first.  */
     368        result = iconv_msgdomain_list (result, po_charset_utf8, true, input_file);
     369      }
     370    else
     371      {
     372        filter = generic_filter;
     373  
     374        /* Warn if the current locale is not suitable for this PO file.  */
     375        compare_po_locale_charsets (result);
     376  
     377        /* Attempt to locate the program.
     378           This is an optimization, to avoid that spawn/exec searches the PATH
     379           on every call.  */
     380        sub_path = find_in_path (sub_name);
     381  
     382        /* Finish argument list for the program.  */
     383        sub_argv[0] = sub_path;
     384      }
     385  
     386    /* Apply the subprogram.  */
     387    result = process_msgdomain_list (result);
     388  
     389    /* Sort the results.  */
     390    if (sort_by_filepos)
     391      msgdomain_list_sort_by_filepos (result);
     392    else if (sort_by_msgid)
     393      msgdomain_list_sort_by_msgid (result);
     394  
     395    /* Write the merged message list out.  */
     396    msgdomain_list_print (result, output_file, output_syntax, force_po, false);
     397  
     398    exit (EXIT_SUCCESS);
     399  }
     400  
     401  
     402  /* Display usage information and exit.  */
     403  static void
     404  usage (int status)
     405  {
     406    if (status != EXIT_SUCCESS)
     407      fprintf (stderr, _("Try '%s --help' for more information.\n"),
     408               program_name);
     409    else
     410      {
     411        printf (_("\
     412  Usage: %s [OPTION] FILTER [FILTER-OPTION]\n\
     413  "), program_name);
     414        printf ("\n");
     415        printf (_("\
     416  Applies a filter to all translations of a translation catalog.\n\
     417  "));
     418        printf ("\n");
     419        printf (_("\
     420  Mandatory arguments to long options are mandatory for short options too.\n"));
     421        printf ("\n");
     422        printf (_("\
     423  Input file location:\n"));
     424        printf (_("\
     425    -i, --input=INPUTFILE       input PO file\n"));
     426        printf (_("\
     427    -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
     428        printf (_("\
     429  If no input file is given or if it is -, standard input is read.\n"));
     430        printf ("\n");
     431        printf (_("\
     432  Output file location:\n"));
     433        printf (_("\
     434    -o, --output-file=FILE      write output to specified file\n"));
     435        printf (_("\
     436  The results are written to standard output if no output file is specified\n\
     437  or if it is -.\n"));
     438        printf ("\n");
     439        printf (_("\
     440  The FILTER can be any program that reads a translation from standard input\n\
     441  and writes a modified translation to standard output.\n\
     442  "));
     443        printf ("\n");
     444        printf (_("\
     445  Filter input and output:\n"));
     446        printf (_("\
     447    --newline                   add a newline at the end of input and\n\
     448                                  remove a newline from the end of output"));
     449        printf ("\n");
     450        printf (_("\
     451  Useful FILTER-OPTIONs when the FILTER is 'sed':\n"));
     452        printf (_("\
     453    -e, --expression=SCRIPT     add SCRIPT to the commands to be executed\n"));
     454        printf (_("\
     455    -f, --file=SCRIPTFILE       add the contents of SCRIPTFILE to the commands\n\
     456                                  to be executed\n"));
     457        printf (_("\
     458    -n, --quiet, --silent       suppress automatic printing of pattern space\n"));
     459        printf ("\n");
     460        printf (_("\
     461  Input file syntax:\n"));
     462        printf (_("\
     463    -P, --properties-input      input file is in Java .properties syntax\n"));
     464        printf (_("\
     465        --stringtable-input     input file is in NeXTstep/GNUstep .strings syntax\n"));
     466        printf ("\n");
     467        printf (_("\
     468  Output details:\n"));
     469        printf (_("\
     470        --color                 use colors and other text attributes always\n\
     471        --color=WHEN            use colors and other text attributes if WHEN.\n\
     472                                WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
     473        printf (_("\
     474        --style=STYLEFILE       specify CSS style rule file for --color\n"));
     475        printf (_("\
     476        --no-escape             do not use C escapes in output (default)\n"));
     477        printf (_("\
     478    -E, --escape                use C escapes in output, no extended chars\n"));
     479        printf (_("\
     480        --force-po              write PO file even if empty\n"));
     481        printf (_("\
     482        --indent                indented output style\n"));
     483        printf (_("\
     484        --keep-header           keep header entry unmodified, don't filter it\n"));
     485        printf (_("\
     486        --no-location           suppress '#: filename:line' lines\n"));
     487        printf (_("\
     488    -n, --add-location          preserve '#: filename:line' lines (default)\n"));
     489        printf (_("\
     490        --strict                strict Uniforum output style\n"));
     491        printf (_("\
     492    -p, --properties-output     write out a Java .properties file\n"));
     493        printf (_("\
     494        --stringtable-output    write out a NeXTstep/GNUstep .strings file\n"));
     495        printf (_("\
     496    -w, --width=NUMBER          set output page width\n"));
     497        printf (_("\
     498        --no-wrap               do not break long message lines, longer than\n\
     499                                the output page width, into several lines\n"));
     500        printf (_("\
     501    -s, --sort-output           generate sorted output\n"));
     502        printf (_("\
     503    -F, --sort-by-file          sort output by file location\n"));
     504        printf ("\n");
     505        printf (_("\
     506  Informative output:\n"));
     507        printf (_("\
     508    -h, --help                  display this help and exit\n"));
     509        printf (_("\
     510    -V, --version               output version information and exit\n"));
     511        printf ("\n");
     512        /* TRANSLATORS: The first placeholder is the web address of the Savannah
     513           project of this package.  The second placeholder is the bug-reporting
     514           email address for this package.  Please add _another line_ saying
     515           "Report translation bugs to <...>\n" with the address for translation
     516           bugs (typically your translation team's web or email address).  */
     517        printf(_("\
     518  Report bugs in the bug tracker at <%s>\n\
     519  or by email to <%s>.\n"),
     520               "https://savannah.gnu.org/projects/gettext",
     521               "bug-gettext@gnu.org");
     522      }
     523  
     524    exit (status);
     525  }
     526  
     527  
     528  /* Callbacks called from pipe_filter_ii_execute.  */
     529  
     530  struct locals
     531  {
     532    /* String being written.  */
     533    const char *str;
     534    size_t len;
     535    /* String being read and accumulated.  */
     536    char *result;
     537    size_t allocated;
     538    size_t length;
     539  };
     540  
     541  static const void *
     542  prepare_write (size_t *num_bytes_p, void *private_data)
     543  {
     544    struct locals *l = (struct locals *) private_data;
     545  
     546    if (l->len > 0)
     547      {
     548        *num_bytes_p = l->len;
     549        return l->str;
     550      }
     551    else
     552      return NULL;
     553  }
     554  
     555  static void
     556  done_write (void *data_written, size_t num_bytes_written, void *private_data)
     557  {
     558    struct locals *l = (struct locals *) private_data;
     559  
     560    l->str += num_bytes_written;
     561    l->len -= num_bytes_written;
     562  }
     563  
     564  static void *
     565  prepare_read (size_t *num_bytes_p, void *private_data)
     566  {
     567    struct locals *l = (struct locals *) private_data;
     568  
     569    if (l->length == l->allocated)
     570      {
     571        l->allocated = l->allocated + (l->allocated >> 1) + 1;
     572        l->result = (char *) xrealloc (l->result, l->allocated);
     573      }
     574    *num_bytes_p = l->allocated - l->length;
     575    return l->result + l->length;
     576  }
     577  
     578  static void
     579  done_read (void *data_read, size_t num_bytes_read, void *private_data)
     580  {
     581    struct locals *l = (struct locals *) private_data;
     582  
     583    l->length += num_bytes_read;
     584  }
     585  
     586  
     587  /* Process a string STR of size LEN bytes through the subprogram.
     588     Store the freshly allocated result at *RESULTP and its length at *LENGTHP.
     589   */
     590  static void
     591  generic_filter (const char *str, size_t len, char **resultp, size_t *lengthp)
     592  {
     593    struct locals l;
     594  
     595    l.str = str;
     596    l.len = len;
     597    l.allocated = len + (len >> 2) + 1;
     598    l.result = XNMALLOC (l.allocated, char);
     599    l.length = 0;
     600  
     601    pipe_filter_ii_execute (sub_name, sub_path, sub_argv, false, true,
     602                            prepare_write, done_write, prepare_read, done_read,
     603                            &l);
     604  
     605    *resultp = l.result;
     606    *lengthp = l.length;
     607  }
     608  
     609  
     610  /* Process a string STR of size LEN bytes, then remove NUL bytes.
     611     Store the freshly allocated result at *RESULTP and its length at *LENGTHP.
     612   */
     613  static void
     614  process_string (const char *str, size_t len, char **resultp, size_t *lengthp)
     615  {
     616    char *result;
     617    size_t length;
     618  
     619    filter (str, len, &result, &length);
     620  
     621    /* Remove NUL bytes from result.  */
     622    {
     623      char *p = result;
     624      char *pend = result + length;
     625  
     626      for (; p < pend; p++)
     627        if (*p == '\0')
     628          {
     629            char *q;
     630  
     631            q = p;
     632            for (; p < pend; p++)
     633              if (*p != '\0')
     634                *q++ = *p;
     635            length = q - result;
     636            break;
     637          }
     638    }
     639  
     640    *resultp = result;
     641    *lengthp = length;
     642  }
     643  
     644  
     645  /* Do the same thing as process_string but append a newline to STR
     646     before processing, and remove a newline from the result.
     647   */
     648  static void
     649  process_string_with_newline (const char *str, size_t len, char **resultp,
     650                               size_t *lengthp)
     651  {
     652    char *newstr;
     653    char *result;
     654    size_t length;
     655  
     656    newstr = XNMALLOC (len + 1, char);
     657    memcpy (newstr, str, len);
     658    newstr[len] = '\n';
     659  
     660    process_string (newstr, len + 1, &result, &length);
     661  
     662    free (newstr);
     663  
     664    if (length > 0 && result[length - 1] == '\n')
     665      result[--length] = '\0';
     666    else
     667      error (0, 0, _("filter output is not terminated with a newline"));
     668  
     669    *resultp = result;
     670    *lengthp = length;
     671  }
     672  
     673  
     674  static void
     675  process_message (message_ty *mp)
     676  {
     677    const char *msgstr = mp->msgstr;
     678    size_t msgstr_len = mp->msgstr_len;
     679    char *location;
     680    size_t nsubstrings;
     681    char **substrings;
     682    size_t total_len;
     683    char *total_str;
     684    const char *p;
     685    char *q;
     686    size_t k;
     687  
     688    /* Keep the header entry unmodified, if --keep-header was given.  */
     689    if (is_header (mp) && keep_header)
     690      return;
     691  
     692    /* Set environment variables for the subprocess.
     693       Note: These environment variables, especially MSGFILTER_MSGCTXT and
     694       MSGFILTER_MSGID, may contain non-ASCII characters.  The subprocess
     695       may not interpret these values correctly if the locale encoding is
     696       different from the PO file's encoding.  We want about this situation,
     697       above.
     698       On Unix, this problem is often harmless.  On Windows, however, - both
     699       native Windows and Cygwin - the values of environment variables *must*
     700       be in the encoding that is the value of GetACP(), because the system
     701       may convert the environment from char** to wchar_t** before spawning
     702       the subprocess and back from wchar_t** to char** in the subprocess,
     703       and it does so using the GetACP() codepage.  */
     704    if (mp->msgctxt != NULL)
     705      xsetenv ("MSGFILTER_MSGCTXT", mp->msgctxt, 1);
     706    else
     707      unsetenv ("MSGFILTER_MSGCTXT");
     708    xsetenv ("MSGFILTER_MSGID", mp->msgid, 1);
     709    if (mp->msgid_plural != NULL)
     710      xsetenv ("MSGFILTER_MSGID_PLURAL", mp->msgid_plural, 1);
     711    else
     712      unsetenv ("MSGFILTER_MSGID_PLURAL");
     713    location = xasprintf ("%s:%ld", mp->pos.file_name,
     714                          (long) mp->pos.line_number);
     715    xsetenv ("MSGFILTER_LOCATION", location, 1);
     716    free (location);
     717    if (mp->prev_msgctxt != NULL)
     718      xsetenv ("MSGFILTER_PREV_MSGCTXT", mp->prev_msgctxt, 1);
     719    else
     720      unsetenv ("MSGFILTER_PREV_MSGCTXT");
     721    if (mp->prev_msgid != NULL)
     722      xsetenv ("MSGFILTER_PREV_MSGID", mp->prev_msgid, 1);
     723    else
     724      unsetenv ("MSGFILTER_PREV_MSGID");
     725    if (mp->prev_msgid_plural != NULL)
     726      xsetenv ("MSGFILTER_PREV_MSGID_PLURAL", mp->prev_msgid_plural, 1);
     727    else
     728      unsetenv ("MSGFILTER_PREV_MSGID_PLURAL");
     729  
     730    /* Count NUL delimited substrings.  */
     731    for (p = msgstr, nsubstrings = 0;
     732         p < msgstr + msgstr_len;
     733         p += strlen (p) + 1, nsubstrings++);
     734  
     735    /* Process each NUL delimited substring separately.  */
     736    substrings = XNMALLOC (nsubstrings, char *);
     737    for (p = msgstr, k = 0, total_len = 0; k < nsubstrings; k++)
     738      {
     739        char *result;
     740        size_t length;
     741  
     742        if (mp->msgid_plural != NULL)
     743          {
     744            char *plural_form_string = xasprintf ("%lu", (unsigned long) k);
     745  
     746            xsetenv ("MSGFILTER_PLURAL_FORM", plural_form_string, 1);
     747            free (plural_form_string);
     748          }
     749        else
     750          unsetenv ("MSGFILTER_PLURAL_FORM");
     751  
     752        if (newline)
     753          process_string_with_newline (p, strlen (p), &result, &length);
     754        else
     755          process_string (p, strlen (p), &result, &length);
     756  
     757        result = (char *) xrealloc (result, length + 1);
     758        result[length] = '\0';
     759        substrings[k] = result;
     760        total_len += length + 1;
     761  
     762        p += strlen (p) + 1;
     763      }
     764  
     765    /* Concatenate the results, including the NUL after each.  */
     766    total_str = XNMALLOC (total_len, char);
     767    for (k = 0, q = total_str; k < nsubstrings; k++)
     768      {
     769        size_t length = strlen (substrings[k]);
     770  
     771        memcpy (q, substrings[k], length + 1);
     772        free (substrings[k]);
     773        q += length + 1;
     774      }
     775    free (substrings);
     776  
     777    mp->msgstr = total_str;
     778    mp->msgstr_len = total_len;
     779  }
     780  
     781  
     782  static void
     783  process_message_list (message_list_ty *mlp)
     784  {
     785    size_t j;
     786  
     787    for (j = 0; j < mlp->nitems; j++)
     788      process_message (mlp->item[j]);
     789  }
     790  
     791  
     792  static msgdomain_list_ty *
     793  process_msgdomain_list (msgdomain_list_ty *mdlp)
     794  {
     795    size_t k;
     796  
     797    for (k = 0; k < mdlp->nitems; k++)
     798      process_message_list (mdlp->item[k]->messages);
     799  
     800    return mdlp;
     801  }