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