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.
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/>. */
32 #include "error-progname.h"
34 #include "relocatable.h"
37 #include "read-catalog.h"
39 #include "read-properties.h"
40 #include "read-stringtable.h"
41 #include "write-catalog.h"
43 #include "write-properties.h"
44 #include "write-stringtable.h"
46 #include "propername.h"
49 #define _(str) gettext (str)
52 /* Force output of PO file even if empty. */
55 /* Bit mask of subsets to remove. */
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
67 /* Bit mask of actions to perform on all messages. */
72 SET_OBSOLETE = 1 << 2,
73 RESET_OBSOLETE = 1 << 3,
79 static const struct option long_options[] =
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', },
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))
127 static msgdomain_list_ty *process_msgdomain_list (msgdomain_list_ty *mdlp,
128 msgdomain_list_ty *only_mdlp,
129 msgdomain_list_ty *ignore_mdlp);
133 main (int argc, char **argv)
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;
150 /* Set program name for messages. */
151 set_program_name (argv[0]);
152 error_print_progname = maybe_print_progname;
154 #ifdef HAVE_SETLOCALE
155 /* Set locale via LC_ALL. */
156 setlocale (LC_ALL, "");
159 /* Set the text message domain. */
160 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
161 bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
162 textdomain (PACKAGE);
164 /* Ensure that write errors on stdout are detected. */
165 atexit (close_stdout);
167 /* Set default values for variables. */
175 while ((optchar = getopt_long (argc, argv, "D:eEFhino:pPsVw:", long_options,
179 case '\0': /* Long option. */
183 dir_list_append (optarg);
187 message_print_style_escape (false);
191 message_print_style_escape (true);
195 sort_by_filepos = true;
203 message_print_style_indent ();
211 output_file = optarg;
215 output_syntax = &output_format_properties;
219 input_syntax = &input_format_properties;
223 sort_by_msgid = true;
227 message_print_style_uniforum ();
238 value = strtol (optarg, &endp, 10);
240 message_page_width_set (value);
244 case CHAR_MAX + 1: /* --translated */
245 to_remove |= REMOVE_UNTRANSLATED;
248 case CHAR_MAX + 2: /* --untranslated */
249 to_remove |= REMOVE_TRANSLATED;
252 case CHAR_MAX + 3: /* --no-fuzzy */
253 to_remove |= REMOVE_FUZZY;
256 case CHAR_MAX + 4: /* --only-fuzzy */
257 to_remove |= REMOVE_NONFUZZY;
260 case CHAR_MAX + 5: /* --no-obsolete */
261 to_remove |= REMOVE_OBSOLETE;
264 case CHAR_MAX + 6: /* --only-obsolete */
265 to_remove |= REMOVE_NONOBSOLETE;
268 case CHAR_MAX + 7: /* --set-fuzzy */
269 to_change |= SET_FUZZY;
272 case CHAR_MAX + 8: /* --clear-fuzzy */
273 to_change |= RESET_FUZZY;
276 case CHAR_MAX + 9: /* --set-obsolete */
277 to_change |= SET_OBSOLETE;
280 case CHAR_MAX + 10: /* --clear-obsolete */
281 to_change |= RESET_OBSOLETE;
284 case CHAR_MAX + 11: /* --fuzzy */
285 to_remove |= REMOVE_NONFUZZY;
286 to_change |= RESET_FUZZY;
289 case CHAR_MAX + 12: /* --obsolete */
290 to_remove |= REMOVE_NONOBSOLETE;
291 to_change |= RESET_OBSOLETE;
294 case CHAR_MAX + 13: /* --no-wrap */
295 message_page_width_ignore ();
298 case CHAR_MAX + 14: /* --only-file */
302 case CHAR_MAX + 15: /* --ignore-file */
303 ignore_file = optarg;
306 case CHAR_MAX + 16: /* --stringtable-input */
307 input_syntax = &input_format_stringtable;
310 case CHAR_MAX + 17: /* --stringtable-output */
311 output_syntax = &output_format_stringtable;
314 case CHAR_MAX + 18: /* --clear-previous */
315 to_change |= REMOVE_PREV;
318 case CHAR_MAX + 19: /* --color */
319 if (handle_color_option (optarg) || color_test_mode)
320 usage (EXIT_FAILURE);
323 case CHAR_MAX + 20: /* --style */
324 handle_style_option (optarg);
328 usage (EXIT_FAILURE);
332 /* Version information requested. */
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\
343 printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
347 /* Help is requested. */
349 usage (EXIT_SUCCESS);
351 /* Test whether we have an .po file name as argument. */
354 else if (optind + 1 == argc)
355 input_file = argv[optind];
358 error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
359 usage (EXIT_FAILURE);
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");
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");
371 /* Read input file. */
372 result = read_catalog_file (input_file, input_syntax);
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)
378 ignore_mdlp = (ignore_file != NULL
379 ? read_catalog_file (ignore_file, input_syntax)
382 /* Filter the messages and manipulate the attributes. */
383 result = process_msgdomain_list (result, only_mdlp, ignore_mdlp);
385 /* Sorting the list of messages. */
387 msgdomain_list_sort_by_filepos (result);
388 else if (sort_by_msgid)
389 msgdomain_list_sort_by_msgid (result);
391 /* Write the PO file. */
392 msgdomain_list_print (result, output_file, output_syntax, force_po, false);
398 /* Display usage information and exit. */
402 if (status != EXIT_SUCCESS)
403 fprintf (stderr, _("Try `%s --help' for more information.\n"),
408 Usage: %s [OPTION] [INPUTFILE]\n\
411 /* xgettext: no-wrap */
413 Filters the messages of a translation catalog according to their attributes,\n\
414 and manipulates the attributes.\n"));
417 Mandatory arguments to long options are mandatory for short options too.\n"));
420 Input file location:\n"));
422 INPUTFILE input PO file\n"));
424 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
426 If no input file is given or if it is -, standard input is read.\n"));
429 Output file location:\n"));
431 -o, --output-file=FILE write output to specified file\n"));
433 The results are written to standard output if no output file is specified\n\
437 Message selection:\n"));
439 --translated keep translated, remove untranslated messages\n"));
441 --untranslated keep untranslated, remove translated messages\n"));
443 --no-fuzzy remove 'fuzzy' marked messages\n"));
445 --only-fuzzy keep 'fuzzy' marked messages\n"));
447 --no-obsolete remove obsolete #~ messages\n"));
449 --only-obsolete keep obsolete #~ messages\n"));
452 Attribute manipulation:\n"));
454 --set-fuzzy set all messages 'fuzzy'\n"));
456 --clear-fuzzy set all messages non-'fuzzy'\n"));
458 --set-obsolete set all messages obsolete\n"));
460 --clear-obsolete set all messages non-obsolete\n"));
462 --clear-previous remove the \"previous msgid\" from all messages\n"));
464 --only-file=FILE.po manipulate only entries listed in FILE.po\n"));
466 --ignore-file=FILE.po manipulate only entries not listed in FILE.po\n"));
468 --fuzzy synonym for --only-fuzzy --clear-fuzzy\n"));
470 --obsolete synonym for --only-obsolete --clear-obsolete\n"));
473 Input file syntax:\n"));
475 -P, --properties-input input file is in Java .properties syntax\n"));
477 --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n"));
480 Output details:\n"));
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"));
486 --style=STYLEFILE specify CSS style rule file for --color\n"));
488 -e, --no-escape do not use C escapes in output (default)\n"));
490 -E, --escape use C escapes in output, no extended chars\n"));
492 --force-po write PO file even if empty\n"));
494 -i, --indent write the .po file using indented style\n"));
496 --no-location do not write '#: filename:line' lines\n"));
498 -n, --add-location generate '#: filename:line' lines (default)\n"));
500 --strict write out strict Uniforum conforming .po file\n"));
502 -p, --properties-output write out a Java .properties file\n"));
504 --stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
506 -w, --width=NUMBER set output page width\n"));
508 --no-wrap do not break long message lines, longer than\n\
509 the output page width, into several lines\n"));
511 -s, --sort-output generate sorted output\n"));
513 -F, --sort-by-file sort output by file location\n"));
516 Informative output:\n"));
518 -h, --help display this help and exit\n"));
520 -V, --version output version information and exit\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"),
534 /* Return true if a message should be kept. */
536 is_message_selected (const message_ty *mp)
538 /* Always keep the header entry. */
542 if ((to_remove & (REMOVE_UNTRANSLATED | REMOVE_TRANSLATED))
543 && (mp->msgstr[0] == '\0'
544 ? to_remove & REMOVE_UNTRANSLATED
545 : to_remove & REMOVE_TRANSLATED))
548 if ((to_remove & (REMOVE_FUZZY | REMOVE_NONFUZZY))
550 ? to_remove & REMOVE_FUZZY
551 : to_remove & REMOVE_NONFUZZY))
554 if ((to_remove & (REMOVE_OBSOLETE | REMOVE_NONOBSOLETE))
556 ? to_remove & REMOVE_OBSOLETE
557 : to_remove & REMOVE_NONOBSOLETE))
565 process_message_list (message_list_ty *mlp,
566 message_list_ty *only_mlp, message_list_ty *ignore_mlp)
568 /* Keep only the selected messages. */
569 message_list_remove_if_not (mlp, is_message_selected);
571 /* Change the attributes. */
576 for (j = 0; j < mlp->nitems; j++)
578 message_ty *mp = mlp->item[j];
580 /* Attribute changes only affect messages listed in --only-file
581 and not listed in --ignore-file. */
583 ? message_list_search (only_mlp, mp->msgctxt, mp->msgid) != NULL
586 ? message_list_search (ignore_mlp, mp->msgctxt, mp->msgid) == NULL
589 if (to_change & SET_FUZZY)
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))
596 if (to_change & RESET_OBSOLETE)
597 mp->obsolete = false;
598 if (to_change & REMOVE_PREV)
600 mp->prev_msgctxt = NULL;
601 mp->prev_msgid = NULL;
602 mp->prev_msgid_plural = NULL;
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)
617 for (k = 0; k < mdlp->nitems; k++)
618 process_message_list (mdlp->item[k]->messages,
620 ? msgdomain_list_sublist (only_mdlp,
621 mdlp->item[k]->domain,
625 ? msgdomain_list_sublist (ignore_mdlp,
626 mdlp->item[k]->domain,