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