77323156329c418a2e6410fdc7577672316b93b8
[platform/upstream/gettext.git] / gettext-tools / src / msgattrib.c
1 /* Manipulates attributes of messages in translation catalogs.
2    Copyright (C) 2001-2007, 2009-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 <stdio.h>
26 #include <stdlib.h>
27 #include <locale.h>
28
29 #include "closeout.h"
30 #include "dir-list.h"
31 #include "error.h"
32 #include "error-progname.h"
33 #include "progname.h"
34 #include "relocatable.h"
35 #include "basename.h"
36 #include "message.h"
37 #include "read-catalog.h"
38 #include "read-po.h"
39 #include "read-properties.h"
40 #include "read-stringtable.h"
41 #include "write-catalog.h"
42 #include "write-po.h"
43 #include "write-properties.h"
44 #include "write-stringtable.h"
45 #include "color.h"
46 #include "propername.h"
47 #include "xalloc.h"
48 #include "gettext.h"
49
50 #define _(str) gettext (str)
51
52
53 /* Force output of PO file even if empty.  */
54 static int force_po;
55
56 /* Bit mask of subsets to remove.  */
57 enum
58 {
59   REMOVE_UNTRANSLATED   = 1 << 0,
60   REMOVE_TRANSLATED     = 1 << 1,
61   REMOVE_FUZZY          = 1 << 2,
62   REMOVE_NONFUZZY       = 1 << 3,
63   REMOVE_OBSOLETE       = 1 << 4,
64   REMOVE_NONOBSOLETE    = 1 << 5
65 };
66 static int to_remove;
67
68 /* Bit mask of actions to perform on all messages.  */
69 enum
70 {
71   SET_FUZZY             = 1 << 0,
72   RESET_FUZZY           = 1 << 1,
73   SET_OBSOLETE          = 1 << 2,
74   RESET_OBSOLETE        = 1 << 3,
75   REMOVE_PREV           = 1 << 4,
76   ADD_PREV              = 1 << 5
77 };
78 static int to_change;
79
80 /* Long options.  */
81 static const struct option long_options[] =
82 {
83   { "add-location", no_argument, &line_comment, 1 },
84   { "clear-fuzzy", no_argument, NULL, CHAR_MAX + 8 },
85   { "clear-obsolete", no_argument, NULL, CHAR_MAX + 10 },
86   { "clear-previous", no_argument, NULL, CHAR_MAX + 18 },
87   { "color", optional_argument, NULL, CHAR_MAX + 19 },
88   { "directory", required_argument, NULL, 'D' },
89   { "escape", no_argument, NULL, 'E' },
90   { "force-po", no_argument, &force_po, 1 },
91   { "fuzzy", no_argument, NULL, CHAR_MAX + 11 },
92   { "help", no_argument, NULL, 'h' },
93   { "ignore-file", required_argument, NULL, CHAR_MAX + 15 },
94   { "indent", no_argument, NULL, 'i' },
95   { "no-escape", no_argument, NULL, 'e' },
96   { "no-fuzzy", no_argument, NULL, CHAR_MAX + 3 },
97   { "no-location", no_argument, &line_comment, 0 },
98   { "no-obsolete", no_argument, NULL, CHAR_MAX + 5 },
99   { "no-wrap", no_argument, NULL, CHAR_MAX + 13 },
100   { "obsolete", no_argument, NULL, CHAR_MAX + 12 },
101   { "only-file", required_argument, NULL, CHAR_MAX + 14 },
102   { "only-fuzzy", no_argument, NULL, CHAR_MAX + 4 },
103   { "only-obsolete", no_argument, NULL, CHAR_MAX + 6 },
104   { "output-file", required_argument, NULL, 'o' },
105   { "previous", no_argument, NULL, CHAR_MAX + 21 },
106   { "properties-input", no_argument, NULL, 'P' },
107   { "properties-output", no_argument, NULL, 'p' },
108   { "set-fuzzy", no_argument, NULL, CHAR_MAX + 7 },
109   { "set-obsolete", no_argument, NULL, CHAR_MAX + 9 },
110   { "sort-by-file", no_argument, NULL, 'F' },
111   { "sort-output", no_argument, NULL, 's' },
112   { "stringtable-input", no_argument, NULL, CHAR_MAX + 16 },
113   { "stringtable-output", no_argument, NULL, CHAR_MAX + 17 },
114   { "strict", no_argument, NULL, 'S' },
115   { "style", required_argument, NULL, CHAR_MAX + 20 },
116   { "translated", no_argument, NULL, CHAR_MAX + 1 },
117   { "untranslated", no_argument, NULL, CHAR_MAX + 2 },
118   { "version", no_argument, NULL, 'V' },
119   { "width", required_argument, NULL, 'w', },
120   { NULL, 0, NULL, 0 }
121 };
122
123
124 /* Forward declaration of local functions.  */
125 static void usage (int status)
126 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
127         __attribute__ ((noreturn))
128 #endif
129 ;
130 static msgdomain_list_ty *process_msgdomain_list (msgdomain_list_ty *mdlp,
131                                                   msgdomain_list_ty *only_mdlp,
132                                                 msgdomain_list_ty *ignore_mdlp);
133
134
135 int
136 main (int argc, char **argv)
137 {
138   int optchar;
139   bool do_help;
140   bool do_version;
141   char *output_file;
142   const char *input_file;
143   const char *only_file;
144   const char *ignore_file;
145   msgdomain_list_ty *only_mdlp;
146   msgdomain_list_ty *ignore_mdlp;
147   msgdomain_list_ty *result;
148   catalog_input_format_ty input_syntax = &input_format_po;
149   catalog_output_format_ty output_syntax = &output_format_po;
150   bool sort_by_msgid = false;
151   bool sort_by_filepos = false;
152
153   /* Set program name for messages.  */
154   set_program_name (argv[0]);
155   error_print_progname = maybe_print_progname;
156
157 #ifdef HAVE_SETLOCALE
158   /* Set locale via LC_ALL.  */
159   setlocale (LC_ALL, "");
160 #endif
161
162   /* Set the text message domain.  */
163   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
164   bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
165   textdomain (PACKAGE);
166
167   /* Ensure that write errors on stdout are detected.  */
168   atexit (close_stdout);
169
170   /* Set default values for variables.  */
171   do_help = false;
172   do_version = false;
173   output_file = NULL;
174   input_file = NULL;
175   only_file = NULL;
176   ignore_file = NULL;
177
178   while ((optchar = getopt_long (argc, argv, "D:eEFhino:pPsVw:", long_options,
179                                  NULL)) != EOF)
180     switch (optchar)
181       {
182       case '\0':                /* Long option.  */
183         break;
184
185       case 'D':
186         dir_list_append (optarg);
187         break;
188
189       case 'e':
190         message_print_style_escape (false);
191         break;
192
193       case 'E':
194         message_print_style_escape (true);
195         break;
196
197       case 'F':
198         sort_by_filepos = true;
199         break;
200
201       case 'h':
202         do_help = true;
203         break;
204
205       case 'i':
206         message_print_style_indent ();
207         break;
208
209       case 'n':
210         line_comment = 1;
211         break;
212
213       case 'o':
214         output_file = optarg;
215         break;
216
217       case 'p':
218         output_syntax = &output_format_properties;
219         break;
220
221       case 'P':
222         input_syntax = &input_format_properties;
223         break;
224
225       case 's':
226         sort_by_msgid = true;
227         break;
228
229       case 'S':
230         message_print_style_uniforum ();
231         break;
232
233       case 'V':
234         do_version = true;
235         break;
236
237       case 'w':
238         {
239           int value;
240           char *endp;
241           value = strtol (optarg, &endp, 10);
242           if (endp != optarg)
243             message_page_width_set (value);
244         }
245         break;
246
247       case CHAR_MAX + 1: /* --translated */
248         to_remove |= REMOVE_UNTRANSLATED;
249         break;
250
251       case CHAR_MAX + 2: /* --untranslated */
252         to_remove |= REMOVE_TRANSLATED;
253         break;
254
255       case CHAR_MAX + 3: /* --no-fuzzy */
256         to_remove |= REMOVE_FUZZY;
257         break;
258
259       case CHAR_MAX + 4: /* --only-fuzzy */
260         to_remove |= REMOVE_NONFUZZY;
261         break;
262
263       case CHAR_MAX + 5: /* --no-obsolete */
264         to_remove |= REMOVE_OBSOLETE;
265         break;
266
267       case CHAR_MAX + 6: /* --only-obsolete */
268         to_remove |= REMOVE_NONOBSOLETE;
269         break;
270
271       case CHAR_MAX + 7: /* --set-fuzzy */
272         to_change |= SET_FUZZY;
273         break;
274
275       case CHAR_MAX + 8: /* --clear-fuzzy */
276         to_change |= RESET_FUZZY;
277         break;
278
279       case CHAR_MAX + 9: /* --set-obsolete */
280         to_change |= SET_OBSOLETE;
281         break;
282
283       case CHAR_MAX + 10: /* --clear-obsolete */
284         to_change |= RESET_OBSOLETE;
285         break;
286
287       case CHAR_MAX + 11: /* --fuzzy */
288         to_remove |= REMOVE_NONFUZZY;
289         to_change |= RESET_FUZZY;
290         break;
291
292       case CHAR_MAX + 12: /* --obsolete */
293         to_remove |= REMOVE_NONOBSOLETE;
294         to_change |= RESET_OBSOLETE;
295         break;
296
297       case CHAR_MAX + 13: /* --no-wrap */
298         message_page_width_ignore ();
299         break;
300
301       case CHAR_MAX + 14: /* --only-file */
302         only_file = optarg;
303         break;
304
305       case CHAR_MAX + 15: /* --ignore-file */
306         ignore_file = optarg;
307         break;
308
309       case CHAR_MAX + 16: /* --stringtable-input */
310         input_syntax = &input_format_stringtable;
311         break;
312
313       case CHAR_MAX + 17: /* --stringtable-output */
314         output_syntax = &output_format_stringtable;
315         break;
316
317       case CHAR_MAX + 18: /* --clear-previous */
318         to_change |= REMOVE_PREV;
319         break;
320
321       case CHAR_MAX + 19: /* --color */
322         if (handle_color_option (optarg) || color_test_mode)
323           usage (EXIT_FAILURE);
324         break;
325
326       case CHAR_MAX + 20: /* --style */
327         handle_style_option (optarg);
328         break;
329
330       case CHAR_MAX + 21: /* --previous */
331         to_change |= ADD_PREV;
332         break;
333
334       default:
335         usage (EXIT_FAILURE);
336         /* NOTREACHED */
337       }
338
339   /* Version information requested.  */
340   if (do_version)
341     {
342       printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
343       /* xgettext: no-wrap */
344       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
345 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
346 This is free software: you are free to change and redistribute it.\n\
347 There is NO WARRANTY, to the extent permitted by law.\n\
348 "),
349               "2001-2010");
350       printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
351       exit (EXIT_SUCCESS);
352     }
353
354   /* Help is requested.  */
355   if (do_help)
356     usage (EXIT_SUCCESS);
357
358   /* Test whether we have an .po file name as argument.  */
359   if (optind == argc)
360     input_file = "-";
361   else if (optind + 1 == argc)
362     input_file = argv[optind];
363   else
364     {
365       error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
366       usage (EXIT_FAILURE);
367     }
368
369   /* Verify selected options.  */
370   if (!line_comment && sort_by_filepos)
371     error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
372            "--no-location", "--sort-by-file");
373
374   if (sort_by_msgid && sort_by_filepos)
375     error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
376            "--sort-output", "--sort-by-file");
377
378   /* Read input file.  */
379   result = read_catalog_file (input_file, input_syntax);
380
381   /* Read optional files that limit the extent of the attribute changes.  */
382   only_mdlp = (only_file != NULL
383                ? read_catalog_file (only_file, input_syntax)
384                : NULL);
385   ignore_mdlp = (ignore_file != NULL
386                  ? read_catalog_file (ignore_file, input_syntax)
387                  : NULL);
388
389   /* Filter the messages and manipulate the attributes.  */
390   result = process_msgdomain_list (result, only_mdlp, ignore_mdlp);
391
392   /* Sorting the list of messages.  */
393   if (sort_by_filepos)
394     msgdomain_list_sort_by_filepos (result);
395   else if (sort_by_msgid)
396     msgdomain_list_sort_by_msgid (result);
397
398   /* Write the PO file.  */
399   msgdomain_list_print (result, output_file, output_syntax, force_po, false);
400
401   exit (EXIT_SUCCESS);
402 }
403
404
405 /* Display usage information and exit.  */
406 static void
407 usage (int status)
408 {
409   if (status != EXIT_SUCCESS)
410     fprintf (stderr, _("Try '%s --help' for more information.\n"),
411              program_name);
412   else
413     {
414       printf (_("\
415 Usage: %s [OPTION] [INPUTFILE]\n\
416 "), program_name);
417       printf ("\n");
418       /* xgettext: no-wrap */
419       printf (_("\
420 Filters the messages of a translation catalog according to their attributes,\n\
421 and manipulates the attributes.\n"));
422       printf ("\n");
423       printf (_("\
424 Mandatory arguments to long options are mandatory for short options too.\n"));
425       printf ("\n");
426       printf (_("\
427 Input file location:\n"));
428       printf (_("\
429   INPUTFILE                   input PO file\n"));
430       printf (_("\
431   -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
432       printf (_("\
433 If no input file is given or if it is -, standard input is read.\n"));
434       printf ("\n");
435       printf (_("\
436 Output file location:\n"));
437       printf (_("\
438   -o, --output-file=FILE      write output to specified file\n"));
439       printf (_("\
440 The results are written to standard output if no output file is specified\n\
441 or if it is -.\n"));
442       printf ("\n");
443       printf (_("\
444 Message selection:\n"));
445       printf (_("\
446       --translated            keep translated, remove untranslated messages\n"));
447       printf (_("\
448       --untranslated          keep untranslated, remove translated messages\n"));
449       printf (_("\
450       --no-fuzzy              remove 'fuzzy' marked messages\n"));
451       printf (_("\
452       --only-fuzzy            keep 'fuzzy' marked messages\n"));
453       printf (_("\
454       --no-obsolete           remove obsolete #~ messages\n"));
455       printf (_("\
456       --only-obsolete         keep obsolete #~ messages\n"));
457       printf ("\n");
458       printf (_("\
459 Attribute manipulation:\n"));
460       printf (_("\
461       --set-fuzzy             set all messages 'fuzzy'\n"));
462       printf (_("\
463       --clear-fuzzy           set all messages non-'fuzzy'\n"));
464       printf (_("\
465       --set-obsolete          set all messages obsolete\n"));
466       printf (_("\
467       --clear-obsolete        set all messages non-obsolete\n"));
468       printf (_("\
469       --previous              when setting 'fuzzy', keep previous msgids\n\
470                               of translated messages.\n"));
471       printf (_("\
472       --clear-previous        remove the \"previous msgid\" from all messages\n"));
473       printf (_("\
474       --only-file=FILE.po     manipulate only entries listed in FILE.po\n"));
475       printf (_("\
476       --ignore-file=FILE.po   manipulate only entries not listed in FILE.po\n"));
477       printf (_("\
478       --fuzzy                 synonym for --only-fuzzy --clear-fuzzy\n"));
479       printf (_("\
480       --obsolete              synonym for --only-obsolete --clear-obsolete\n"));
481       printf ("\n");
482       printf (_("\
483 Input file syntax:\n"));
484       printf (_("\
485   -P, --properties-input      input file is in Java .properties syntax\n"));
486       printf (_("\
487       --stringtable-input     input file is in NeXTstep/GNUstep .strings syntax\n"));
488       printf ("\n");
489       printf (_("\
490 Output details:\n"));
491       printf (_("\
492       --color                 use colors and other text attributes always\n\
493       --color=WHEN            use colors and other text attributes if WHEN.\n\
494                               WHEN may be 'always', 'never', 'auto', or 'html'.\n"));
495       printf (_("\
496       --style=STYLEFILE       specify CSS style rule file for --color\n"));
497       printf (_("\
498   -e, --no-escape             do not use C escapes in output (default)\n"));
499       printf (_("\
500   -E, --escape                use C escapes in output, no extended chars\n"));
501       printf (_("\
502       --force-po              write PO file even if empty\n"));
503       printf (_("\
504   -i, --indent                write the .po file using indented style\n"));
505       printf (_("\
506       --no-location           do not write '#: filename:line' lines\n"));
507       printf (_("\
508   -n, --add-location          generate '#: filename:line' lines (default)\n"));
509       printf (_("\
510       --strict                write out strict Uniforum conforming .po file\n"));
511       printf (_("\
512   -p, --properties-output     write out a Java .properties file\n"));
513       printf (_("\
514       --stringtable-output    write out a NeXTstep/GNUstep .strings file\n"));
515       printf (_("\
516   -w, --width=NUMBER          set output page width\n"));
517       printf (_("\
518       --no-wrap               do not break long message lines, longer than\n\
519                               the output page width, into several lines\n"));
520       printf (_("\
521   -s, --sort-output           generate sorted output\n"));
522       printf (_("\
523   -F, --sort-by-file          sort output by file location\n"));
524       printf ("\n");
525       printf (_("\
526 Informative output:\n"));
527       printf (_("\
528   -h, --help                  display this help and exit\n"));
529       printf (_("\
530   -V, --version               output version information and exit\n"));
531       printf ("\n");
532       /* TRANSLATORS: The placeholder indicates the bug-reporting address
533          for this package.  Please add _another line_ saying
534          "Report translation bugs to <...>\n" with the address for translation
535          bugs (typically your translation team's web or email address).  */
536       fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
537              stdout);
538     }
539
540   exit (status);
541 }
542
543
544 /* Return true if a message should be kept.  */
545 static bool
546 is_message_selected (const message_ty *mp)
547 {
548   /* Always keep the header entry.  */
549   if (is_header (mp))
550     return true;
551
552   if ((to_remove & (REMOVE_UNTRANSLATED | REMOVE_TRANSLATED))
553       && (mp->msgstr[0] == '\0'
554           ? to_remove & REMOVE_UNTRANSLATED
555           : to_remove & REMOVE_TRANSLATED))
556     return false;
557
558   if ((to_remove & (REMOVE_FUZZY | REMOVE_NONFUZZY))
559       && (mp->is_fuzzy
560           ? to_remove & REMOVE_FUZZY
561           : to_remove & REMOVE_NONFUZZY))
562     return false;
563
564   if ((to_remove & (REMOVE_OBSOLETE | REMOVE_NONOBSOLETE))
565       && (mp->obsolete
566           ? to_remove & REMOVE_OBSOLETE
567           : to_remove & REMOVE_NONOBSOLETE))
568     return false;
569
570   return true;
571 }
572
573
574 static void
575 process_message_list (message_list_ty *mlp,
576                       message_list_ty *only_mlp, message_list_ty *ignore_mlp)
577 {
578   /* Keep only the selected messages.  */
579   message_list_remove_if_not (mlp, is_message_selected);
580
581   /* Change the attributes.  */
582   if (to_change)
583     {
584       size_t j;
585
586       for (j = 0; j < mlp->nitems; j++)
587         {
588           message_ty *mp = mlp->item[j];
589
590           /* Attribute changes only affect messages listed in --only-file
591              and not listed in --ignore-file.  */
592           if ((only_mlp
593                ? message_list_search (only_mlp, mp->msgctxt, mp->msgid) != NULL
594                : true)
595               && (ignore_mlp
596                   ? message_list_search (ignore_mlp, mp->msgctxt, mp->msgid) == NULL
597                   : true))
598             {
599               if (to_change & SET_FUZZY)
600                 {
601                   if ((to_change & ADD_PREV) && !is_header (mp)
602                       && !mp->is_fuzzy && mp->msgstr[0] != '\0')
603                     {
604                       mp->prev_msgctxt =
605                         (mp->msgctxt != NULL ? xstrdup (mp->msgctxt) : NULL);
606                       mp->prev_msgid =
607                         (mp->msgid != NULL ? xstrdup (mp->msgid) : NULL);
608                       mp->prev_msgid_plural =
609                         (mp->msgid_plural != NULL
610                          ? xstrdup (mp->msgid_plural)
611                          : NULL);
612                     }
613                   mp->is_fuzzy = true;
614                 }
615
616               if (to_change & RESET_FUZZY)
617                 mp->is_fuzzy = false;
618               /* Always keep the header entry non-obsolete.  */
619               if ((to_change & SET_OBSOLETE) && !is_header (mp))
620                 mp->obsolete = true;
621               if (to_change & RESET_OBSOLETE)
622                 mp->obsolete = false;
623               if (to_change & REMOVE_PREV)
624                 {
625                   mp->prev_msgctxt = NULL;
626                   mp->prev_msgid = NULL;
627                   mp->prev_msgid_plural = NULL;
628                 }
629             }
630         }
631     }
632 }
633
634
635 static msgdomain_list_ty *
636 process_msgdomain_list (msgdomain_list_ty *mdlp,
637                         msgdomain_list_ty *only_mdlp,
638                         msgdomain_list_ty *ignore_mdlp)
639 {
640   size_t k;
641
642   for (k = 0; k < mdlp->nitems; k++)
643     process_message_list (mdlp->item[k]->messages,
644                           only_mdlp
645                           ? msgdomain_list_sublist (only_mdlp,
646                                                     mdlp->item[k]->domain,
647                                                     true)
648                           : NULL,
649                           ignore_mdlp
650                           ? msgdomain_list_sublist (ignore_mdlp,
651                                                     mdlp->item[k]->domain,
652                                                     false)
653                           : NULL);
654
655   return mdlp;
656 }