1 /* Edit translations using a subprocess.
2 Copyright (C) 2001-2010, 2012 Free Software Foundation, Inc.
3 Written by Bruno Haible <haible@clisp.cons.org>, 2001.
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.
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.
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/>. */
29 #include <sys/types.h>
36 #include "xvasprintf.h"
37 #include "error-progname.h"
39 #include "relocatable.h"
42 #include "read-catalog.h"
44 #include "read-properties.h"
45 #include "read-stringtable.h"
46 #include "write-catalog.h"
48 #include "write-properties.h"
49 #include "write-stringtable.h"
51 #include "msgl-charset.h"
54 #include "pipe-filter.h"
57 #include "msgl-iconv.h"
58 #include "po-charset.h"
59 #include "propername.h"
62 #define _(str) gettext (str)
65 /* We use a child process, and communicate through a bidirectional pipe. */
68 /* Force output of PO file even if empty. */
71 /* Keep the header entry unmodified. */
72 static int keep_header;
74 /* Name of the subprogram. */
75 static const char *sub_name;
77 /* Pathname of the subprogram. */
78 static const char *sub_path;
80 /* Argument list for the subprogram. */
81 static const char **sub_argv;
84 /* Filter function. */
85 static void (*filter) (const char *str, size_t len, char **resultp, size_t *lengthp);
88 static const struct option long_options[] =
90 { "add-location", no_argument, &line_comment, 1 },
91 { "color", optional_argument, NULL, CHAR_MAX + 6 },
92 { "directory", required_argument, NULL, 'D' },
93 { "escape", no_argument, NULL, 'E' },
94 { "force-po", no_argument, &force_po, 1 },
95 { "help", no_argument, NULL, 'h' },
96 { "indent", no_argument, NULL, CHAR_MAX + 1 },
97 { "input", required_argument, NULL, 'i' },
98 { "keep-header", no_argument, &keep_header, 1 },
99 { "no-escape", no_argument, NULL, CHAR_MAX + 2 },
100 { "no-location", no_argument, &line_comment, 0 },
101 { "no-wrap", no_argument, NULL, CHAR_MAX + 3 },
102 { "output-file", required_argument, NULL, 'o' },
103 { "properties-input", no_argument, NULL, 'P' },
104 { "properties-output", no_argument, NULL, 'p' },
105 { "sort-by-file", no_argument, NULL, 'F' },
106 { "sort-output", no_argument, NULL, 's' },
107 { "strict", no_argument, NULL, 'S' },
108 { "stringtable-input", no_argument, NULL, CHAR_MAX + 4 },
109 { "stringtable-output", no_argument, NULL, CHAR_MAX + 5 },
110 { "style", required_argument, NULL, CHAR_MAX + 7 },
111 { "version", no_argument, NULL, 'V' },
112 { "width", required_argument, NULL, 'w', },
117 /* Forward declaration of local functions. */
118 static void usage (int status)
119 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
120 __attribute__ ((noreturn))
123 static void generic_filter (const char *str, size_t len, char **resultp, size_t *lengthp);
124 static msgdomain_list_ty *process_msgdomain_list (msgdomain_list_ty *mdlp);
128 main (int argc, char **argv)
134 const char *input_file;
135 msgdomain_list_ty *result;
136 catalog_input_format_ty input_syntax = &input_format_po;
137 catalog_output_format_ty output_syntax = &output_format_po;
138 bool sort_by_filepos = false;
139 bool sort_by_msgid = false;
142 /* Set program name for messages. */
143 set_program_name (argv[0]);
144 error_print_progname = maybe_print_progname;
146 #ifdef HAVE_SETLOCALE
147 /* Set locale via LC_ALL. */
148 setlocale (LC_ALL, "");
151 /* Set the text message domain. */
152 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
153 bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
154 textdomain (PACKAGE);
156 /* Ensure that write errors on stdout are detected. */
157 atexit (close_stdout);
159 /* Set default values for variables. */
165 /* The '+' in the options string causes option parsing to terminate when
166 the first non-option, i.e. the subprogram name, is encountered. */
167 while ((opt = getopt_long (argc, argv, "+D:EFhi:o:pPsVw:", long_options,
172 case '\0': /* Long option. */
176 dir_list_append (optarg);
180 message_print_style_escape (true);
184 sort_by_filepos = true;
192 if (input_file != NULL)
194 error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
195 usage (EXIT_FAILURE);
201 output_file = optarg;
205 output_syntax = &output_format_properties;
209 input_syntax = &input_format_properties;
213 sort_by_msgid = true;
217 message_print_style_uniforum ();
228 value = strtol (optarg, &endp, 10);
230 message_page_width_set (value);
235 message_print_style_indent ();
239 message_print_style_escape (false);
242 case CHAR_MAX + 3: /* --no-wrap */
243 message_page_width_ignore ();
246 case CHAR_MAX + 4: /* --stringtable-input */
247 input_syntax = &input_format_stringtable;
250 case CHAR_MAX + 5: /* --stringtable-output */
251 output_syntax = &output_format_stringtable;
254 case CHAR_MAX + 6: /* --color */
255 if (handle_color_option (optarg) || color_test_mode)
256 usage (EXIT_FAILURE);
259 case CHAR_MAX + 7: /* --style */
260 handle_style_option (optarg);
264 usage (EXIT_FAILURE);
268 /* Version information is requested. */
271 printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
272 /* xgettext: no-wrap */
273 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
274 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
275 This is free software: you are free to change and redistribute it.\n\
276 There is NO WARRANTY, to the extent permitted by law.\n\
279 printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
283 /* Help is requested. */
285 usage (EXIT_SUCCESS);
287 /* Test for the subprogram name. */
289 error (EXIT_FAILURE, 0, _("missing filter name"));
290 sub_name = argv[optind];
292 /* Verify selected options. */
293 if (!line_comment && sort_by_filepos)
294 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
295 "--no-location", "--sort-by-file");
297 if (sort_by_msgid && sort_by_filepos)
298 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
299 "--sort-output", "--sort-by-file");
301 /* Build argument list for the program. */
302 sub_argc = argc - optind;
303 sub_argv = XNMALLOC (sub_argc + 1, const char *);
304 for (i = 0; i < sub_argc; i++)
305 sub_argv[i] = argv[optind + i];
308 /* Extra checks for sed scripts. */
309 if (strcmp (sub_name, "sed") == 0)
312 error (EXIT_FAILURE, 0,
313 _("at least one sed script must be specified"));
315 /* Replace GNU sed specific options with portable sed options. */
316 for (i = 1; i < sub_argc; i++)
318 if (strcmp (sub_argv[i], "--expression") == 0)
320 else if (strcmp (sub_argv[i], "--file") == 0)
322 else if (strcmp (sub_argv[i], "--quiet") == 0
323 || strcmp (sub_argv[i], "--silent") == 0)
326 if (strcmp (sub_argv[i], "-e") == 0
327 || strcmp (sub_argv[i], "-f") == 0)
332 /* By default, input comes from standard input. */
333 if (input_file == NULL)
336 /* Read input file. */
337 result = read_catalog_file (input_file, input_syntax);
339 /* Recognize special programs as built-ins. */
340 if (strcmp (sub_name, "recode-sr-latin") == 0 && sub_argc == 1)
342 filter = serbian_to_latin;
344 /* Convert the input to UTF-8 first. */
345 result = iconv_msgdomain_list (result, po_charset_utf8, true, input_file);
349 filter = generic_filter;
351 /* Warn if the current locale is not suitable for this PO file. */
352 compare_po_locale_charsets (result);
354 /* Attempt to locate the program.
355 This is an optimization, to avoid that spawn/exec searches the PATH
357 sub_path = find_in_path (sub_name);
359 /* Finish argument list for the program. */
360 sub_argv[0] = sub_path;
363 /* Apply the subprogram. */
364 result = process_msgdomain_list (result);
366 /* Sort the results. */
368 msgdomain_list_sort_by_filepos (result);
369 else if (sort_by_msgid)
370 msgdomain_list_sort_by_msgid (result);
372 /* Write the merged message list out. */
373 msgdomain_list_print (result, output_file, output_syntax, force_po, false);
379 /* Display usage information and exit. */
383 if (status != EXIT_SUCCESS)
384 fprintf (stderr, _("Try '%s --help' for more information.\n"),
389 Usage: %s [OPTION] FILTER [FILTER-OPTION]\n\
393 Applies a filter to all translations of a translation catalog.\n\
397 Mandatory arguments to long options are mandatory for short options too.\n"));
400 Input file location:\n"));
402 -i, --input=INPUTFILE input PO file\n"));
404 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
406 If no input file is given or if it is -, standard input is read.\n"));
409 Output file location:\n"));
411 -o, --output-file=FILE write output to specified file\n"));
413 The results are written to standard output if no output file is specified\n\
417 The FILTER can be any program that reads a translation from standard input\n\
418 and writes a modified translation to standard output.\n\
422 Useful FILTER-OPTIONs when the FILTER is 'sed':\n"));
424 -e, --expression=SCRIPT add SCRIPT to the commands to be executed\n"));
426 -f, --file=SCRIPTFILE add the contents of SCRIPTFILE to the commands\n\
429 -n, --quiet, --silent suppress automatic printing of pattern space\n"));
432 Input file syntax:\n"));
434 -P, --properties-input input file is in Java .properties syntax\n"));
436 --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n"));
439 Output details:\n"));
441 --color use colors and other text attributes always\n\
442 --color=WHEN use colors and other text attributes if WHEN.\n\
443 WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
445 --style=STYLEFILE specify CSS style rule file for --color\n"));
447 --no-escape do not use C escapes in output (default)\n"));
449 -E, --escape use C escapes in output, no extended chars\n"));
451 --force-po write PO file even if empty\n"));
453 --indent indented output style\n"));
455 --keep-header keep header entry unmodified, don't filter it\n"));
457 --no-location suppress '#: filename:line' lines\n"));
459 --add-location preserve '#: filename:line' lines (default)\n"));
461 --strict strict Uniforum output style\n"));
463 -p, --properties-output write out a Java .properties file\n"));
465 --stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
467 -w, --width=NUMBER set output page width\n"));
469 --no-wrap do not break long message lines, longer than\n\
470 the output page width, into several lines\n"));
472 -s, --sort-output generate sorted output\n"));
474 -F, --sort-by-file sort output by file location\n"));
477 Informative output:\n"));
479 -h, --help display this help and exit\n"));
481 -V, --version output version information and exit\n"));
483 /* TRANSLATORS: The placeholder indicates the bug-reporting address
484 for this package. Please add _another line_ saying
485 "Report translation bugs to <...>\n" with the address for translation
486 bugs (typically your translation team's web or email address). */
487 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
495 /* Callbacks called from pipe_filter_ii_execute. */
499 /* String being written. */
502 /* String being read and accumulated. */
509 prepare_write (size_t *num_bytes_p, void *private_data)
511 struct locals *l = (struct locals *) private_data;
515 *num_bytes_p = l->len;
523 done_write (void *data_written, size_t num_bytes_written, void *private_data)
525 struct locals *l = (struct locals *) private_data;
527 l->str += num_bytes_written;
528 l->len -= num_bytes_written;
532 prepare_read (size_t *num_bytes_p, void *private_data)
534 struct locals *l = (struct locals *) private_data;
536 if (l->length == l->allocated)
538 l->allocated = l->allocated + (l->allocated >> 1);
539 l->result = (char *) xrealloc (l->result, l->allocated);
541 *num_bytes_p = l->allocated - l->length;
542 return l->result + l->length;
546 done_read (void *data_read, size_t num_bytes_read, void *private_data)
548 struct locals *l = (struct locals *) private_data;
550 l->length += num_bytes_read;
554 /* Process a string STR of size LEN bytes through the subprogram.
555 Store the freshly allocated result at *RESULTP and its length at *LENGTHP.
558 generic_filter (const char *str, size_t len, char **resultp, size_t *lengthp)
564 l.allocated = len + (len >> 2) + 1;
565 l.result = XNMALLOC (l.allocated, char);
568 pipe_filter_ii_execute (sub_name, sub_path, sub_argv, false, true,
569 prepare_write, done_write, prepare_read, done_read,
577 /* Process a string STR of size LEN bytes, then remove NUL bytes.
578 Store the freshly allocated result at *RESULTP and its length at *LENGTHP.
581 process_string (const char *str, size_t len, char **resultp, size_t *lengthp)
586 filter (str, len, &result, &length);
588 /* Remove NUL bytes from result. */
591 char *pend = result + length;
593 for (; p < pend; p++)
599 for (; p < pend; p++)
613 process_message (message_ty *mp)
615 const char *msgstr = mp->msgstr;
616 size_t msgstr_len = mp->msgstr_len;
626 /* Keep the header entry unmodified, if --keep-header was given. */
627 if (is_header (mp) && keep_header)
630 /* Set environment variables for the subprocess.
631 Note: These environment variables, especially MSGEXEC_MSGCTXT and
632 MSGEXEC_MSGCTXT, may contain non-ASCII characters. The subprocess
633 may not interpret these values correctly if the locale encoding is
634 different from the PO file's encoding. We want about this situation,
636 On Unix, this problem is often harmless. On Windows, however, - both
637 native Windows and Cygwin - the values of environment variables *must*
638 be in the encoding that is the value of GetACP(), because the system
639 may convert the environment from char** to wchar_t** before spawning
640 the subprocess and back from wchar_t** to char** in the subprocess,
641 and it does so using the GetACP() codepage. */
642 if (mp->msgctxt != NULL)
643 xsetenv ("MSGFILTER_MSGCTXT", mp->msgctxt, 1);
645 unsetenv ("MSGFILTER_MSGCTXT");
646 xsetenv ("MSGFILTER_MSGID", mp->msgid, 1);
647 location = xasprintf ("%s:%ld", mp->pos.file_name,
648 (long) mp->pos.line_number);
649 xsetenv ("MSGFILTER_LOCATION", location, 1);
652 /* Count NUL delimited substrings. */
653 for (p = msgstr, nsubstrings = 0;
654 p < msgstr + msgstr_len;
655 p += strlen (p) + 1, nsubstrings++);
657 /* Process each NUL delimited substring separately. */
658 substrings = XNMALLOC (nsubstrings, char *);
659 for (p = msgstr, k = 0, total_len = 0; k < nsubstrings; k++)
664 process_string (p, strlen (p), &result, &length);
665 result = (char *) xrealloc (result, length + 1);
666 result[length] = '\0';
667 substrings[k] = result;
668 total_len += length + 1;
673 /* Concatenate the results, including the NUL after each. */
674 total_str = XNMALLOC (total_len, char);
675 for (k = 0, q = total_str; k < nsubstrings; k++)
677 size_t length = strlen (substrings[k]);
679 memcpy (q, substrings[k], length + 1);
680 free (substrings[k]);
685 mp->msgstr = total_str;
686 mp->msgstr_len = total_len;
691 process_message_list (message_list_ty *mlp)
695 for (j = 0; j < mlp->nitems; j++)
696 process_message (mlp->item[j]);
700 static msgdomain_list_ty *
701 process_msgdomain_list (msgdomain_list_ty *mdlp)
705 for (k = 0; k < mdlp->nitems; k++)
706 process_message_list (mdlp->item[k]->messages);