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