Imported Upstream version 0.18.3.2
[platform/upstream/gettext.git] / gettext-tools / src / msgfilter.c
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.
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 <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 "closeout.h"
34 #include "dir-list.h"
35 #include "error.h"
36 #include "xvasprintf.h"
37 #include "error-progname.h"
38 #include "progname.h"
39 #include "relocatable.h"
40 #include "basename.h"
41 #include "message.h"
42 #include "read-catalog.h"
43 #include "read-po.h"
44 #include "read-properties.h"
45 #include "read-stringtable.h"
46 #include "write-catalog.h"
47 #include "write-po.h"
48 #include "write-properties.h"
49 #include "write-stringtable.h"
50 #include "color.h"
51 #include "msgl-charset.h"
52 #include "xalloc.h"
53 #include "findprog.h"
54 #include "pipe-filter.h"
55 #include "xsetenv.h"
56 #include "filters.h"
57 #include "msgl-iconv.h"
58 #include "po-charset.h"
59 #include "propername.h"
60 #include "gettext.h"
61
62 #define _(str) gettext (str)
63
64
65 /* We use a child process, and communicate through a bidirectional pipe.  */
66
67
68 /* Force output of PO file even if empty.  */
69 static int force_po;
70
71 /* Keep the header entry unmodified.  */
72 static int keep_header;
73
74 /* Name of the subprogram.  */
75 static const char *sub_name;
76
77 /* Pathname of the subprogram.  */
78 static const char *sub_path;
79
80 /* Argument list for the subprogram.  */
81 static const char **sub_argv;
82 static int sub_argc;
83
84 /* Filter function.  */
85 static void (*filter) (const char *str, size_t len, char **resultp, size_t *lengthp);
86
87 /* Long options.  */
88 static const struct option long_options[] =
89 {
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', },
113   { NULL, 0, NULL, 0 }
114 };
115
116
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))
121 #endif
122 ;
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);
125
126
127 int
128 main (int argc, char **argv)
129 {
130   int opt;
131   bool do_help;
132   bool do_version;
133   char *output_file;
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;
140   int i;
141
142   /* Set program name for messages.  */
143   set_program_name (argv[0]);
144   error_print_progname = maybe_print_progname;
145
146 #ifdef HAVE_SETLOCALE
147   /* Set locale via LC_ALL.  */
148   setlocale (LC_ALL, "");
149 #endif
150
151   /* Set the text message domain.  */
152   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
153   bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
154   textdomain (PACKAGE);
155
156   /* Ensure that write errors on stdout are detected.  */
157   atexit (close_stdout);
158
159   /* Set default values for variables.  */
160   do_help = false;
161   do_version = false;
162   output_file = NULL;
163   input_file = NULL;
164
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,
168                              NULL))
169          != EOF)
170     switch (opt)
171       {
172       case '\0':                /* Long option.  */
173         break;
174
175       case 'D':
176         dir_list_append (optarg);
177         break;
178
179       case 'E':
180         message_print_style_escape (true);
181         break;
182
183       case 'F':
184         sort_by_filepos = true;
185         break;
186
187       case 'h':
188         do_help = true;
189         break;
190
191       case 'i':
192         if (input_file != NULL)
193           {
194             error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
195             usage (EXIT_FAILURE);
196           }
197         input_file = optarg;
198         break;
199
200       case 'o':
201         output_file = optarg;
202         break;
203
204       case 'p':
205         output_syntax = &output_format_properties;
206         break;
207
208       case 'P':
209         input_syntax = &input_format_properties;
210         break;
211
212       case 's':
213         sort_by_msgid = true;
214         break;
215
216       case 'S':
217         message_print_style_uniforum ();
218         break;
219
220       case 'V':
221         do_version = true;
222         break;
223
224       case 'w':
225         {
226           int value;
227           char *endp;
228           value = strtol (optarg, &endp, 10);
229           if (endp != optarg)
230             message_page_width_set (value);
231         }
232         break;
233
234       case CHAR_MAX + 1:
235         message_print_style_indent ();
236         break;
237
238       case CHAR_MAX + 2:
239         message_print_style_escape (false);
240         break;
241
242       case CHAR_MAX + 3: /* --no-wrap */
243         message_page_width_ignore ();
244         break;
245
246       case CHAR_MAX + 4: /* --stringtable-input */
247         input_syntax = &input_format_stringtable;
248         break;
249
250       case CHAR_MAX + 5: /* --stringtable-output */
251         output_syntax = &output_format_stringtable;
252         break;
253
254       case CHAR_MAX + 6: /* --color */
255         if (handle_color_option (optarg) || color_test_mode)
256           usage (EXIT_FAILURE);
257         break;
258
259       case CHAR_MAX + 7: /* --style */
260         handle_style_option (optarg);
261         break;
262
263       default:
264         usage (EXIT_FAILURE);
265         break;
266       }
267
268   /* Version information is requested.  */
269   if (do_version)
270     {
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\
277 "),
278               "2001-2010");
279       printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
280       exit (EXIT_SUCCESS);
281     }
282
283   /* Help is requested.  */
284   if (do_help)
285     usage (EXIT_SUCCESS);
286
287   /* Test for the subprogram name.  */
288   if (optind == argc)
289     error (EXIT_FAILURE, 0, _("missing filter name"));
290   sub_name = argv[optind];
291
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");
296
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");
300
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];
306   sub_argv[i] = NULL;
307
308   /* Extra checks for sed scripts.  */
309   if (strcmp (sub_name, "sed") == 0)
310     {
311       if (sub_argc == 1)
312         error (EXIT_FAILURE, 0,
313                _("at least one sed script must be specified"));
314
315       /* Replace GNU sed specific options with portable sed options.  */
316       for (i = 1; i < sub_argc; i++)
317         {
318           if (strcmp (sub_argv[i], "--expression") == 0)
319             sub_argv[i] = "-e";
320           else if (strcmp (sub_argv[i], "--file") == 0)
321             sub_argv[i] = "-f";
322           else if (strcmp (sub_argv[i], "--quiet") == 0
323                    || strcmp (sub_argv[i], "--silent") == 0)
324             sub_argv[i] = "-n";
325
326           if (strcmp (sub_argv[i], "-e") == 0
327               || strcmp (sub_argv[i], "-f") == 0)
328             i++;
329         }
330     }
331
332   /* By default, input comes from standard input.  */
333   if (input_file == NULL)
334     input_file = "-";
335
336   /* Read input file.  */
337   result = read_catalog_file (input_file, input_syntax);
338
339   /* Recognize special programs as built-ins.  */
340   if (strcmp (sub_name, "recode-sr-latin") == 0 && sub_argc == 1)
341     {
342       filter = serbian_to_latin;
343
344       /* Convert the input to UTF-8 first.  */
345       result = iconv_msgdomain_list (result, po_charset_utf8, true, input_file);
346     }
347   else
348     {
349       filter = generic_filter;
350
351       /* Warn if the current locale is not suitable for this PO file.  */
352       compare_po_locale_charsets (result);
353
354       /* Attempt to locate the program.
355          This is an optimization, to avoid that spawn/exec searches the PATH
356          on every call.  */
357       sub_path = find_in_path (sub_name);
358
359       /* Finish argument list for the program.  */
360       sub_argv[0] = sub_path;
361     }
362
363   /* Apply the subprogram.  */
364   result = process_msgdomain_list (result);
365
366   /* Sort the results.  */
367   if (sort_by_filepos)
368     msgdomain_list_sort_by_filepos (result);
369   else if (sort_by_msgid)
370     msgdomain_list_sort_by_msgid (result);
371
372   /* Write the merged message list out.  */
373   msgdomain_list_print (result, output_file, output_syntax, force_po, false);
374
375   exit (EXIT_SUCCESS);
376 }
377
378
379 /* Display usage information and exit.  */
380 static void
381 usage (int status)
382 {
383   if (status != EXIT_SUCCESS)
384     fprintf (stderr, _("Try '%s --help' for more information.\n"),
385              program_name);
386   else
387     {
388       printf (_("\
389 Usage: %s [OPTION] FILTER [FILTER-OPTION]\n\
390 "), program_name);
391       printf ("\n");
392       printf (_("\
393 Applies a filter to all translations of a translation catalog.\n\
394 "));
395       printf ("\n");
396       printf (_("\
397 Mandatory arguments to long options are mandatory for short options too.\n"));
398       printf ("\n");
399       printf (_("\
400 Input file location:\n"));
401       printf (_("\
402   -i, --input=INPUTFILE       input PO file\n"));
403       printf (_("\
404   -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
405       printf (_("\
406 If no input file is given or if it is -, standard input is read.\n"));
407       printf ("\n");
408       printf (_("\
409 Output file location:\n"));
410       printf (_("\
411   -o, --output-file=FILE      write output to specified file\n"));
412       printf (_("\
413 The results are written to standard output if no output file is specified\n\
414 or if it is -.\n"));
415       printf ("\n");
416       printf (_("\
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\
419 "));
420       printf ("\n");
421       printf (_("\
422 Useful FILTER-OPTIONs when the FILTER is 'sed':\n"));
423       printf (_("\
424   -e, --expression=SCRIPT     add SCRIPT to the commands to be executed\n"));
425       printf (_("\
426   -f, --file=SCRIPTFILE       add the contents of SCRIPTFILE to the commands\n\
427                                 to be executed\n"));
428       printf (_("\
429   -n, --quiet, --silent       suppress automatic printing of pattern space\n"));
430       printf ("\n");
431       printf (_("\
432 Input file syntax:\n"));
433       printf (_("\
434   -P, --properties-input      input file is in Java .properties syntax\n"));
435       printf (_("\
436       --stringtable-input     input file is in NeXTstep/GNUstep .strings syntax\n"));
437       printf ("\n");
438       printf (_("\
439 Output details:\n"));
440       printf (_("\
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"));
444       printf (_("\
445       --style=STYLEFILE       specify CSS style rule file for --color\n"));
446       printf (_("\
447       --no-escape             do not use C escapes in output (default)\n"));
448       printf (_("\
449   -E, --escape                use C escapes in output, no extended chars\n"));
450       printf (_("\
451       --force-po              write PO file even if empty\n"));
452       printf (_("\
453       --indent                indented output style\n"));
454       printf (_("\
455       --keep-header           keep header entry unmodified, don't filter it\n"));
456       printf (_("\
457       --no-location           suppress '#: filename:line' lines\n"));
458       printf (_("\
459       --add-location          preserve '#: filename:line' lines (default)\n"));
460       printf (_("\
461       --strict                strict Uniforum output style\n"));
462       printf (_("\
463   -p, --properties-output     write out a Java .properties file\n"));
464       printf (_("\
465       --stringtable-output    write out a NeXTstep/GNUstep .strings file\n"));
466       printf (_("\
467   -w, --width=NUMBER          set output page width\n"));
468       printf (_("\
469       --no-wrap               do not break long message lines, longer than\n\
470                               the output page width, into several lines\n"));
471       printf (_("\
472   -s, --sort-output           generate sorted output\n"));
473       printf (_("\
474   -F, --sort-by-file          sort output by file location\n"));
475       printf ("\n");
476       printf (_("\
477 Informative output:\n"));
478       printf (_("\
479   -h, --help                  display this help and exit\n"));
480       printf (_("\
481   -V, --version               output version information and exit\n"));
482       printf ("\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"),
488              stdout);
489     }
490
491   exit (status);
492 }
493
494
495 /* Callbacks called from pipe_filter_ii_execute.  */
496
497 struct locals
498 {
499   /* String being written.  */
500   const char *str;
501   size_t len;
502   /* String being read and accumulated.  */
503   char *result;
504   size_t allocated;
505   size_t length;
506 };
507
508 static const void *
509 prepare_write (size_t *num_bytes_p, void *private_data)
510 {
511   struct locals *l = (struct locals *) private_data;
512
513   if (l->len > 0)
514     {
515       *num_bytes_p = l->len;
516       return l->str;
517     }
518   else
519     return NULL;
520 }
521
522 static void
523 done_write (void *data_written, size_t num_bytes_written, void *private_data)
524 {
525   struct locals *l = (struct locals *) private_data;
526
527   l->str += num_bytes_written;
528   l->len -= num_bytes_written;
529 }
530
531 static void *
532 prepare_read (size_t *num_bytes_p, void *private_data)
533 {
534   struct locals *l = (struct locals *) private_data;
535
536   if (l->length == l->allocated)
537     {
538       l->allocated = l->allocated + (l->allocated >> 1);
539       l->result = (char *) xrealloc (l->result, l->allocated);
540     }
541   *num_bytes_p = l->allocated - l->length;
542   return l->result + l->length;
543 }
544
545 static void
546 done_read (void *data_read, size_t num_bytes_read, void *private_data)
547 {
548   struct locals *l = (struct locals *) private_data;
549
550   l->length += num_bytes_read;
551 }
552
553
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.
556  */
557 static void
558 generic_filter (const char *str, size_t len, char **resultp, size_t *lengthp)
559 {
560   struct locals l;
561
562   l.str = str;
563   l.len = len;
564   l.allocated = len + (len >> 2) + 1;
565   l.result = XNMALLOC (l.allocated, char);
566   l.length = 0;
567
568   pipe_filter_ii_execute (sub_name, sub_path, sub_argv, false, true,
569                           prepare_write, done_write, prepare_read, done_read,
570                           &l);
571
572   *resultp = l.result;
573   *lengthp = l.length;
574 }
575
576
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.
579  */
580 static void
581 process_string (const char *str, size_t len, char **resultp, size_t *lengthp)
582 {
583   char *result;
584   size_t length;
585
586   filter (str, len, &result, &length);
587
588   /* Remove NUL bytes from result.  */
589   {
590     char *p = result;
591     char *pend = result + length;
592
593     for (; p < pend; p++)
594       if (*p == '\0')
595         {
596           char *q;
597
598           q = p;
599           for (; p < pend; p++)
600             if (*p != '\0')
601               *q++ = *p;
602           length = q - result;
603           break;
604         }
605   }
606
607   *resultp = result;
608   *lengthp = length;
609 }
610
611
612 static void
613 process_message (message_ty *mp)
614 {
615   const char *msgstr = mp->msgstr;
616   size_t msgstr_len = mp->msgstr_len;
617   char *location;
618   size_t nsubstrings;
619   char **substrings;
620   size_t total_len;
621   char *total_str;
622   const char *p;
623   char *q;
624   size_t k;
625
626   /* Keep the header entry unmodified, if --keep-header was given.  */
627   if (is_header (mp) && keep_header)
628     return;
629
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,
635      above.
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);
644   else
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);
650   free (location);
651
652   /* Count NUL delimited substrings.  */
653   for (p = msgstr, nsubstrings = 0;
654        p < msgstr + msgstr_len;
655        p += strlen (p) + 1, nsubstrings++);
656
657   /* Process each NUL delimited substring separately.  */
658   substrings = XNMALLOC (nsubstrings, char *);
659   for (p = msgstr, k = 0, total_len = 0; k < nsubstrings; k++)
660     {
661       char *result;
662       size_t length;
663
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;
669
670       p += strlen (p) + 1;
671     }
672
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++)
676     {
677       size_t length = strlen (substrings[k]);
678
679       memcpy (q, substrings[k], length + 1);
680       free (substrings[k]);
681       q += length + 1;
682     }
683   free (substrings);
684
685   mp->msgstr = total_str;
686   mp->msgstr_len = total_len;
687 }
688
689
690 static void
691 process_message_list (message_list_ty *mlp)
692 {
693   size_t j;
694
695   for (j = 0; j < mlp->nitems; j++)
696     process_message (mlp->item[j]);
697 }
698
699
700 static msgdomain_list_ty *
701 process_msgdomain_list (msgdomain_list_ty *mdlp)
702 {
703   size_t k;
704
705   for (k = 0; k < mdlp->nitems; k++)
706     process_message_list (mdlp->item[k]->messages);
707
708   return mdlp;
709 }