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