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