• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-WNDR4500v2-V1.0.0.60_1.0.38/ap/gpl/timemachine/gettext-0.17/gettext-tools/src/
1/* Edit translations using a subprocess.
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
23#include <errno.h>
24#include <fcntl.h>
25#include <getopt.h>
26#include <limits.h>
27#include <locale.h>
28#include <stdio.h>
29#include <stdlib.h>
30#include <string.h>
31#include <sys/types.h>
32#include <sys/time.h>
33#include <unistd.h>
34#if defined _MSC_VER || defined __MINGW32__
35# include <io.h>
36#endif
37
38/* Get fd_set (on AIX or Minix) or select() declaration (on EMX).  */
39#if defined (_AIX) || defined (_MINIX) || defined (__EMX__)
40# include <sys/select.h>
41#endif
42
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.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 "msgl-charset.h"
60#include "xalloc.h"
61#include "findprog.h"
62#include "pipe.h"
63#include "wait-process.h"
64#include "filters.h"
65#include "msgl-iconv.h"
66#include "po-charset.h"
67#include "propername.h"
68#include "gettext.h"
69
70#define _(str) gettext (str)
71
72
73/* We use a child process, and communicate through a bidirectional pipe.
74   To avoid deadlocks, let the child process decide when it wants to read
75   or to write, and let the parent behave accordingly.  The parent uses
76   select() to know whether it must write or read.  On platforms without
77   select(), we use non-blocking I/O.  (This means the parent is busy
78   looping while waiting for the child.  Not good.)  */
79
80/* On BeOS select() works only on sockets, not on normal file descriptors.  */
81#ifdef __BEOS__
82# undef HAVE_SELECT
83#endif
84
85
86/* Force output of PO file even if empty.  */
87static int force_po;
88
89/* Keep the header entry unmodified.  */
90static int keep_header;
91
92/* Name of the subprogram.  */
93static const char *sub_name;
94
95/* Pathname of the subprogram.  */
96static const char *sub_path;
97
98/* Argument list for the subprogram.  */
99static char **sub_argv;
100static int sub_argc;
101
102/* Filter function.  */
103static void (*filter) (const char *str, size_t len, char **resultp, size_t *lengthp);
104
105/* Long options.  */
106static const struct option long_options[] =
107{
108  { "add-location", no_argument, &line_comment, 1 },
109  { "directory", required_argument, NULL, 'D' },
110  { "escape", no_argument, NULL, 'E' },
111  { "force-po", no_argument, &force_po, 1 },
112  { "help", no_argument, NULL, 'h' },
113  { "indent", no_argument, NULL, CHAR_MAX + 1 },
114  { "input", required_argument, NULL, 'i' },
115  { "keep-header", no_argument, &keep_header, 1 },
116  { "no-escape", no_argument, NULL, CHAR_MAX + 2 },
117  { "no-location", no_argument, &line_comment, 0 },
118  { "no-wrap", no_argument, NULL, CHAR_MAX + 3 },
119  { "output-file", required_argument, NULL, 'o' },
120  { "properties-input", no_argument, NULL, 'P' },
121  { "properties-output", no_argument, NULL, 'p' },
122  { "sort-by-file", no_argument, NULL, 'F' },
123  { "sort-output", no_argument, NULL, 's' },
124  { "strict", no_argument, NULL, 'S' },
125  { "stringtable-input", no_argument, NULL, CHAR_MAX + 4 },
126  { "stringtable-output", no_argument, NULL, CHAR_MAX + 5 },
127  { "version", no_argument, NULL, 'V' },
128  { "width", required_argument, NULL, 'w', },
129  { NULL, 0, NULL, 0 }
130};
131
132
133/* Forward declaration of local functions.  */
134static void usage (int status)
135#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
136	__attribute__ ((noreturn))
137#endif
138;
139static void generic_filter (const char *str, size_t len, char **resultp, size_t *lengthp);
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  msgdomain_list_ty *result;
152  catalog_input_format_ty input_syntax = &input_format_po;
153  catalog_output_format_ty output_syntax = &output_format_po;
154  bool sort_by_filepos = false;
155  bool sort_by_msgid = false;
156  int i;
157
158  /* Set program name for messages.  */
159  set_program_name (argv[0]);
160  error_print_progname = maybe_print_progname;
161
162#ifdef HAVE_SETLOCALE
163  /* Set locale via LC_ALL.  */
164  setlocale (LC_ALL, "");
165#endif
166
167  /* Set the text message domain.  */
168  bindtextdomain (PACKAGE, relocate (LOCALEDIR));
169  bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
170  textdomain (PACKAGE);
171
172  /* Ensure that write errors on stdout are detected.  */
173  atexit (close_stdout);
174
175  /* Set default values for variables.  */
176  do_help = false;
177  do_version = false;
178  output_file = NULL;
179  input_file = NULL;
180
181  /* The '+' in the options string causes option parsing to terminate when
182     the first non-option, i.e. the subprogram name, is encountered.  */
183  while ((opt = getopt_long (argc, argv, "+D:EFhi:o:pPsVw:", long_options,
184			     NULL))
185	 != EOF)
186    switch (opt)
187      {
188      case '\0':		/* Long option.  */
189	break;
190
191      case 'D':
192	dir_list_append (optarg);
193	break;
194
195      case 'E':
196	message_print_style_escape (true);
197	break;
198
199      case 'F':
200	sort_by_filepos = true;
201	break;
202
203      case 'h':
204	do_help = true;
205	break;
206
207      case 'i':
208	if (input_file != NULL)
209	  {
210	    error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
211	    usage (EXIT_FAILURE);
212	  }
213	input_file = optarg;
214	break;
215
216      case 'o':
217	output_file = optarg;
218	break;
219
220      case 'p':
221	output_syntax = &output_format_properties;
222	break;
223
224      case 'P':
225	input_syntax = &input_format_properties;
226	break;
227
228      case 's':
229	sort_by_msgid = true;
230	break;
231
232      case 'S':
233	message_print_style_uniforum ();
234	break;
235
236      case 'V':
237	do_version = true;
238	break;
239
240      case 'w':
241	{
242	  int value;
243	  char *endp;
244	  value = strtol (optarg, &endp, 10);
245	  if (endp != optarg)
246	    message_page_width_set (value);
247	}
248	break;
249
250      case CHAR_MAX + 1:
251	message_print_style_indent ();
252	break;
253
254      case CHAR_MAX + 2:
255	message_print_style_escape (false);
256	break;
257
258      case CHAR_MAX + 3: /* --no-wrap */
259	message_page_width_ignore ();
260	break;
261
262      case CHAR_MAX + 4: /* --stringtable-input */
263	input_syntax = &input_format_stringtable;
264	break;
265
266      case CHAR_MAX + 5: /* --stringtable-output */
267	output_syntax = &output_format_stringtable;
268	break;
269
270      default:
271	usage (EXIT_FAILURE);
272	break;
273      }
274
275  /* Version information is requested.  */
276  if (do_version)
277    {
278      printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
279      /* xgettext: no-wrap */
280      printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
281License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
282This is free software: you are free to change and redistribute it.\n\
283There is NO WARRANTY, to the extent permitted by law.\n\
284"),
285	      "2001-2007");
286      printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
287      exit (EXIT_SUCCESS);
288    }
289
290  /* Help is requested.  */
291  if (do_help)
292    usage (EXIT_SUCCESS);
293
294  /* Test for the subprogram name.  */
295  if (optind == argc)
296    error (EXIT_FAILURE, 0, _("missing filter name"));
297  sub_name = argv[optind];
298
299  /* Verify selected options.  */
300  if (!line_comment && sort_by_filepos)
301    error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
302	   "--no-location", "--sort-by-file");
303
304  if (sort_by_msgid && sort_by_filepos)
305    error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
306	   "--sort-output", "--sort-by-file");
307
308  /* Build argument list for the program.  */
309  sub_argc = argc - optind;
310  sub_argv = XNMALLOC (sub_argc + 1, char *);
311  for (i = 0; i < sub_argc; i++)
312    sub_argv[i] = argv[optind + i];
313  sub_argv[i] = NULL;
314
315  /* Extra checks for sed scripts.  */
316  if (strcmp (sub_name, "sed") == 0)
317    {
318      if (sub_argc == 1)
319	error (EXIT_FAILURE, 0,
320	       _("at least one sed script must be specified"));
321
322      /* Replace GNU sed specific options with portable sed options.  */
323      for (i = 1; i < sub_argc; i++)
324	{
325	  if (strcmp (sub_argv[i], "--expression") == 0)
326	    sub_argv[i] = "-e";
327	  else if (strcmp (sub_argv[i], "--file") == 0)
328	    sub_argv[i] = "-f";
329	  else if (strcmp (sub_argv[i], "--quiet") == 0
330		   || strcmp (sub_argv[i], "--silent") == 0)
331	    sub_argv[i] = "-n";
332
333	  if (strcmp (sub_argv[i], "-e") == 0
334	      || strcmp (sub_argv[i], "-f") == 0)
335	    i++;
336	}
337    }
338
339  /* By default, input comes from standard input.  */
340  if (input_file == NULL)
341    input_file = "-";
342
343  /* Read input file.  */
344  result = read_catalog_file (input_file, input_syntax);
345
346  /* Recognize special programs as built-ins.  */
347  if (strcmp (sub_name, "recode-sr-latin") == 0 && sub_argc == 1)
348    {
349      filter = serbian_to_latin;
350
351      /* Convert the input to UTF-8 first.  */
352      result = iconv_msgdomain_list (result, po_charset_utf8, true, input_file);
353    }
354  else
355    {
356      filter = generic_filter;
357
358      /* Warn if the current locale is not suitable for this PO file.  */
359      compare_po_locale_charsets (result);
360
361      /* Attempt to locate the program.
362	 This is an optimization, to avoid that spawn/exec searches the PATH
363	 on every call.  */
364      sub_path = find_in_path (sub_name);
365
366      /* Finish argument list for the program.  */
367      sub_argv[0] = (char *) sub_path;
368    }
369
370  /* Apply the subprogram.  */
371  result = process_msgdomain_list (result);
372
373  /* Sort the results.  */
374  if (sort_by_filepos)
375    msgdomain_list_sort_by_filepos (result);
376  else if (sort_by_msgid)
377    msgdomain_list_sort_by_msgid (result);
378
379  /* Write the merged message list out.  */
380  msgdomain_list_print (result, output_file, output_syntax, force_po, false);
381
382  exit (EXIT_SUCCESS);
383}
384
385
386/* Display usage information and exit.  */
387static void
388usage (int status)
389{
390  if (status != EXIT_SUCCESS)
391    fprintf (stderr, _("Try `%s --help' for more information.\n"),
392	     program_name);
393  else
394    {
395      printf (_("\
396Usage: %s [OPTION] FILTER [FILTER-OPTION]\n\
397"), program_name);
398      printf ("\n");
399      printf (_("\
400Applies a filter to all translations of a translation catalog.\n\
401"));
402      printf ("\n");
403      printf (_("\
404Mandatory arguments to long options are mandatory for short options too.\n"));
405      printf ("\n");
406      printf (_("\
407Input file location:\n"));
408      printf (_("\
409  -i, --input=INPUTFILE       input PO file\n"));
410      printf (_("\
411  -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
412      printf (_("\
413If no input file is given or if it is -, standard input is read.\n"));
414      printf ("\n");
415      printf (_("\
416Output file location:\n"));
417      printf (_("\
418  -o, --output-file=FILE      write output to specified file\n"));
419      printf (_("\
420The results are written to standard output if no output file is specified\n\
421or if it is -.\n"));
422      printf ("\n");
423      printf (_("\
424The FILTER can be any program that reads a translation from standard input\n\
425and writes a modified translation to standard output.\n\
426"));
427      printf ("\n");
428      printf (_("\
429Useful FILTER-OPTIONs when the FILTER is 'sed':\n"));
430      printf (_("\
431  -e, --expression=SCRIPT     add SCRIPT to the commands to be executed\n"));
432      printf (_("\
433  -f, --file=SCRIPTFILE       add the contents of SCRIPTFILE to the commands\n\
434                                to be executed\n"));
435      printf (_("\
436  -n, --quiet, --silent       suppress automatic printing of pattern space\n"));
437      printf ("\n");
438      printf (_("\
439Input file syntax:\n"));
440      printf (_("\
441  -P, --properties-input      input file is in Java .properties syntax\n"));
442      printf (_("\
443      --stringtable-input     input file is in NeXTstep/GNUstep .strings syntax\n"));
444      printf ("\n");
445      printf (_("\
446Output details:\n"));
447      printf (_("\
448      --no-escape             do not use C escapes in output (default)\n"));
449      printf (_("\
450  -E, --escape                use C escapes in output, no extended chars\n"));
451      printf (_("\
452      --force-po              write PO file even if empty\n"));
453      printf (_("\
454      --indent                indented output style\n"));
455      printf (_("\
456      --keep-header           keep header entry unmodified, don't filter it\n"));
457      printf (_("\
458      --no-location           suppress '#: filename:line' lines\n"));
459      printf (_("\
460      --add-location          preserve '#: filename:line' lines (default)\n"));
461      printf (_("\
462      --strict                strict Uniforum output style\n"));
463      printf (_("\
464  -p, --properties-output     write out a Java .properties file\n"));
465      printf (_("\
466      --stringtable-output    write out a NeXTstep/GNUstep .strings file\n"));
467      printf (_("\
468  -w, --width=NUMBER          set output page width\n"));
469      printf (_("\
470      --no-wrap               do not break long message lines, longer than\n\
471                              the output page width, into several lines\n"));
472      printf (_("\
473  -s, --sort-output           generate sorted output\n"));
474      printf (_("\
475  -F, --sort-by-file          sort output by file location\n"));
476      printf ("\n");
477      printf (_("\
478Informative output:\n"));
479      printf (_("\
480  -h, --help                  display this help and exit\n"));
481      printf (_("\
482  -V, --version               output version information and exit\n"));
483      printf ("\n");
484      /* TRANSLATORS: The placeholder indicates the bug-reporting address
485         for this package.  Please add _another line_ saying
486         "Report translation bugs to <...>\n" with the address for translation
487         bugs (typically your translation team's web or email address).  */
488      fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
489	     stdout);
490    }
491
492  exit (status);
493}
494
495
496#ifdef EINTR
497
498/* EINTR handling for close(), read(), write(), select().
499   These functions can return -1/EINTR even though we don't have any
500   signal handlers set up, namely when we get interrupted via SIGSTOP.  */
501
502static inline int
503nonintr_close (int fd)
504{
505  int retval;
506
507  do
508    retval = close (fd);
509  while (retval < 0 && errno == EINTR);
510
511  return retval;
512}
513#define close nonintr_close
514
515static inline ssize_t
516nonintr_read (int fd, void *buf, size_t count)
517{
518  ssize_t retval;
519
520  do
521    retval = read (fd, buf, count);
522  while (retval < 0 && errno == EINTR);
523
524  return retval;
525}
526#define read nonintr_read
527
528static inline ssize_t
529nonintr_write (int fd, const void *buf, size_t count)
530{
531  ssize_t retval;
532
533  do
534    retval = write (fd, buf, count);
535  while (retval < 0 && errno == EINTR);
536
537  return retval;
538}
539#undef write /* avoid warning on VMS */
540#define write nonintr_write
541
542# if HAVE_SELECT
543
544static inline int
545nonintr_select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,
546		struct timeval *timeout)
547{
548  int retval;
549
550  do
551    retval = select (n, readfds, writefds, exceptfds, timeout);
552  while (retval < 0 && errno == EINTR);
553
554  return retval;
555}
556#undef select /* avoid warning on VMS */
557#define select nonintr_select
558
559# endif
560
561#endif
562
563
564/* Non-blocking I/O.  */
565#ifndef O_NONBLOCK
566# define O_NONBLOCK O_NDELAY
567#endif
568#if HAVE_SELECT
569# define IS_EAGAIN(errcode) 0
570#else
571# ifdef EWOULDBLOCK
572#  define IS_EAGAIN(errcode) ((errcode) == EAGAIN || (errcode) == EWOULDBLOCK)
573# else
574#  define IS_EAGAIN(errcode) ((errcode) == EAGAIN)
575# endif
576#endif
577
578/* Process a string STR of size LEN bytes through the subprogram.
579   Store the freshly allocated result at *RESULTP and its length at *LENGTHP.
580 */
581static void
582generic_filter (const char *str, size_t len, char **resultp, size_t *lengthp)
583{
584#if defined _MSC_VER || defined __MINGW32__
585  /* Native Woe32 API.  */
586  /* Not yet implemented.  */
587  error (EXIT_FAILURE, 0, _("Not yet implemented."));
588#else
589  pid_t child;
590  int fd[2];
591  char *result;
592  size_t allocated;
593  size_t length;
594  int exitstatus;
595
596  /* Open a bidirectional pipe to a subprocess.  */
597  child = create_pipe_bidi (sub_name, sub_path, sub_argv, false, true, true,
598			    fd);
599
600  /* Enable non-blocking I/O.  This permits the read() and write() calls
601     to return -1/EAGAIN without blocking; this is important for polling
602     if HAVE_SELECT is not defined.  It also permits the read() and write()
603     calls to return after partial reads/writes; this is important if
604     HAVE_SELECT is defined, because select() only says that some data
605     can be read or written, not how many.  Without non-blocking I/O,
606     Linux 2.2.17 and BSD systems prefer to block instead of returning
607     with partial results.  */
608  {
609    int fcntl_flags;
610
611    if ((fcntl_flags = fcntl (fd[1], F_GETFL, 0)) < 0
612	|| fcntl (fd[1], F_SETFL, fcntl_flags | O_NONBLOCK) < 0
613	|| (fcntl_flags = fcntl (fd[0], F_GETFL, 0)) < 0
614	|| fcntl (fd[0], F_SETFL, fcntl_flags | O_NONBLOCK) < 0)
615      error (EXIT_FAILURE, errno,
616	     _("cannot set up nonblocking I/O to %s subprocess"), sub_name);
617  }
618
619  allocated = len + (len >> 2) + 1;
620  result = XNMALLOC (allocated, char);
621  length = 0;
622
623  for (;;)
624    {
625#if HAVE_SELECT
626      int n;
627      fd_set readfds;
628      fd_set writefds;
629
630      FD_ZERO (&readfds);
631      FD_SET (fd[0], &readfds);
632      n = fd[0] + 1;
633      if (str != NULL)
634	{
635	  FD_ZERO (&writefds);
636	  FD_SET (fd[1], &writefds);
637	  if (n <= fd[1])
638	    n = fd[1] + 1;
639	}
640
641      n = select (n, &readfds, (str != NULL ? &writefds : NULL), NULL, NULL);
642      if (n < 0)
643	error (EXIT_FAILURE, errno,
644	       _("communication with %s subprocess failed"), sub_name);
645      if (str != NULL && FD_ISSET (fd[1], &writefds))
646	goto try_write;
647      if (FD_ISSET (fd[0], &readfds))
648	goto try_read;
649      /* How could select() return if none of the two descriptors is ready?  */
650      abort ();
651#endif
652
653      /* Attempt to write.  */
654#if HAVE_SELECT
655    try_write:
656#endif
657      if (str != NULL)
658	{
659	  if (len > 0)
660	    {
661	      ssize_t nwritten = write (fd[1], str, len);
662	      if (nwritten < 0 && !IS_EAGAIN (errno))
663		error (EXIT_FAILURE, errno,
664		       _("write to %s subprocess failed"), sub_name);
665	      if (nwritten > 0)
666		{
667		  str += nwritten;
668		  len -= nwritten;
669		}
670	    }
671	  else
672	    {
673	      /* Tell the child there is nothing more the parent will send.  */
674	      close (fd[1]);
675	      str = NULL;
676	    }
677	}
678#if HAVE_SELECT
679      continue;
680#endif
681
682      /* Attempt to read.  */
683#if HAVE_SELECT
684    try_read:
685#endif
686      if (length == allocated)
687	{
688	  allocated = allocated + (allocated >> 1);
689	  result = (char *) xrealloc (result, allocated);
690	}
691      {
692	ssize_t nread = read (fd[0], result + length, allocated - length);
693	if (nread < 0 && !IS_EAGAIN (errno))
694	  error (EXIT_FAILURE, errno,
695		 _("read from %s subprocess failed"), sub_name);
696	if (nread > 0)
697	  length += nread;
698	if (nread == 0 && str == NULL)
699	  break;
700      }
701#if HAVE_SELECT
702      continue;
703#endif
704    }
705
706  close (fd[0]);
707
708  /* Remove zombie process from process list.  */
709  exitstatus = wait_subprocess (child, sub_name, false, false, true, true);
710  if (exitstatus != 0)
711    error (EXIT_FAILURE, 0, _("%s subprocess terminated with exit code %d"),
712	   sub_name, exitstatus);
713
714  *resultp = result;
715  *lengthp = length;
716#endif
717}
718
719
720/* Process a string STR of size LEN bytes, then remove NUL bytes.
721   Store the freshly allocated result at *RESULTP and its length at *LENGTHP.
722 */
723static void
724process_string (const char *str, size_t len, char **resultp, size_t *lengthp)
725{
726  char *result;
727  size_t length;
728
729  filter (str, len, &result, &length);
730
731  /* Remove NUL bytes from result.  */
732  {
733    char *p = result;
734    char *pend = result + length;
735
736    for (; p < pend; p++)
737      if (*p == '\0')
738	{
739	  char *q;
740
741	  q = p;
742	  for (; p < pend; p++)
743	    if (*p != '\0')
744	      *q++ = *p;
745	  length = q - result;
746	  break;
747	}
748  }
749
750  *resultp = result;
751  *lengthp = length;
752}
753
754
755static void
756process_message (message_ty *mp)
757{
758  const char *msgstr = mp->msgstr;
759  size_t msgstr_len = mp->msgstr_len;
760  size_t nsubstrings;
761  char **substrings;
762  size_t total_len;
763  char *total_str;
764  const char *p;
765  char *q;
766  size_t k;
767
768  /* Keep the header entry unmodified, if --keep-header was given.  */
769  if (is_header (mp) && keep_header)
770    return;
771
772  /* Count NUL delimited substrings.  */
773  for (p = msgstr, nsubstrings = 0;
774       p < msgstr + msgstr_len;
775       p += strlen (p) + 1, nsubstrings++);
776
777  /* Process each NUL delimited substring separately.  */
778  substrings = XNMALLOC (nsubstrings, char *);
779  for (p = msgstr, k = 0, total_len = 0; k < nsubstrings; k++)
780    {
781      char *result;
782      size_t length;
783
784      process_string (p, strlen (p), &result, &length);
785      result = (char *) xrealloc (result, length + 1);
786      result[length] = '\0';
787      substrings[k] = result;
788      total_len += length + 1;
789
790      p += strlen (p) + 1;
791    }
792
793  /* Concatenate the results, including the NUL after each.  */
794  total_str = XNMALLOC (total_len, char);
795  for (k = 0, q = total_str; k < nsubstrings; k++)
796    {
797      size_t length = strlen (substrings[k]);
798
799      memcpy (q, substrings[k], length + 1);
800      free (substrings[k]);
801      q += length + 1;
802    }
803  free (substrings);
804
805  mp->msgstr = total_str;
806  mp->msgstr_len = total_len;
807}
808
809
810static void
811process_message_list (message_list_ty *mlp)
812{
813  size_t j;
814
815  for (j = 0; j < mlp->nitems; j++)
816    process_message (mlp->item[j]);
817}
818
819
820static msgdomain_list_ty *
821process_msgdomain_list (msgdomain_list_ty *mdlp)
822{
823  size_t k;
824
825  for (k = 0; k < mdlp->nitems; k++)
826    process_message_list (mdlp->item[k]->messages);
827
828  return mdlp;
829}
830