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