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.
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"
50 #define _(str) gettext (str)
53 /* Force output of PO file even if empty. */
56 /* Bit mask of subsets to remove. */
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
68 /* Bit mask of actions to perform on all messages. */
73 SET_OBSOLETE = 1 << 2,
74 RESET_OBSOLETE = 1 << 3,
81 static const struct option long_options[] =
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', },
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))
130 static msgdomain_list_ty *process_msgdomain_list (msgdomain_list_ty *mdlp,
131 msgdomain_list_ty *only_mdlp,
132 msgdomain_list_ty *ignore_mdlp);
136 main (int argc, char **argv)
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;
153 /* Set program name for messages. */
154 set_program_name (argv[0]);
155 error_print_progname = maybe_print_progname;
157 #ifdef HAVE_SETLOCALE
158 /* Set locale via LC_ALL. */
159 setlocale (LC_ALL, "");
162 /* Set the text message domain. */
163 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
164 bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
165 textdomain (PACKAGE);
167 /* Ensure that write errors on stdout are detected. */
168 atexit (close_stdout);
170 /* Set default values for variables. */
178 while ((optchar = getopt_long (argc, argv, "D:eEFhino:pPsVw:", long_options,
182 case '\0': /* Long option. */
186 dir_list_append (optarg);
190 message_print_style_escape (false);
194 message_print_style_escape (true);
198 sort_by_filepos = true;
206 message_print_style_indent ();
214 output_file = optarg;
218 output_syntax = &output_format_properties;
222 input_syntax = &input_format_properties;
226 sort_by_msgid = true;
230 message_print_style_uniforum ();
241 value = strtol (optarg, &endp, 10);
243 message_page_width_set (value);
247 case CHAR_MAX + 1: /* --translated */
248 to_remove |= REMOVE_UNTRANSLATED;
251 case CHAR_MAX + 2: /* --untranslated */
252 to_remove |= REMOVE_TRANSLATED;
255 case CHAR_MAX + 3: /* --no-fuzzy */
256 to_remove |= REMOVE_FUZZY;
259 case CHAR_MAX + 4: /* --only-fuzzy */
260 to_remove |= REMOVE_NONFUZZY;
263 case CHAR_MAX + 5: /* --no-obsolete */
264 to_remove |= REMOVE_OBSOLETE;
267 case CHAR_MAX + 6: /* --only-obsolete */
268 to_remove |= REMOVE_NONOBSOLETE;
271 case CHAR_MAX + 7: /* --set-fuzzy */
272 to_change |= SET_FUZZY;
275 case CHAR_MAX + 8: /* --clear-fuzzy */
276 to_change |= RESET_FUZZY;
279 case CHAR_MAX + 9: /* --set-obsolete */
280 to_change |= SET_OBSOLETE;
283 case CHAR_MAX + 10: /* --clear-obsolete */
284 to_change |= RESET_OBSOLETE;
287 case CHAR_MAX + 11: /* --fuzzy */
288 to_remove |= REMOVE_NONFUZZY;
289 to_change |= RESET_FUZZY;
292 case CHAR_MAX + 12: /* --obsolete */
293 to_remove |= REMOVE_NONOBSOLETE;
294 to_change |= RESET_OBSOLETE;
297 case CHAR_MAX + 13: /* --no-wrap */
298 message_page_width_ignore ();
301 case CHAR_MAX + 14: /* --only-file */
305 case CHAR_MAX + 15: /* --ignore-file */
306 ignore_file = optarg;
309 case CHAR_MAX + 16: /* --stringtable-input */
310 input_syntax = &input_format_stringtable;
313 case CHAR_MAX + 17: /* --stringtable-output */
314 output_syntax = &output_format_stringtable;
317 case CHAR_MAX + 18: /* --clear-previous */
318 to_change |= REMOVE_PREV;
321 case CHAR_MAX + 19: /* --color */
322 if (handle_color_option (optarg) || color_test_mode)
323 usage (EXIT_FAILURE);
326 case CHAR_MAX + 20: /* --style */
327 handle_style_option (optarg);
330 case CHAR_MAX + 21: /* --previous */
331 to_change |= ADD_PREV;
335 usage (EXIT_FAILURE);
339 /* Version information requested. */
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\
350 printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
354 /* Help is requested. */
356 usage (EXIT_SUCCESS);
358 /* Test whether we have an .po file name as argument. */
361 else if (optind + 1 == argc)
362 input_file = argv[optind];
365 error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
366 usage (EXIT_FAILURE);
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");
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");
378 /* Read input file. */
379 result = read_catalog_file (input_file, input_syntax);
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)
385 ignore_mdlp = (ignore_file != NULL
386 ? read_catalog_file (ignore_file, input_syntax)
389 /* Filter the messages and manipulate the attributes. */
390 result = process_msgdomain_list (result, only_mdlp, ignore_mdlp);
392 /* Sorting the list of messages. */
394 msgdomain_list_sort_by_filepos (result);
395 else if (sort_by_msgid)
396 msgdomain_list_sort_by_msgid (result);
398 /* Write the PO file. */
399 msgdomain_list_print (result, output_file, output_syntax, force_po, false);
405 /* Display usage information and exit. */
409 if (status != EXIT_SUCCESS)
410 fprintf (stderr, _("Try '%s --help' for more information.\n"),
415 Usage: %s [OPTION] [INPUTFILE]\n\
418 /* xgettext: no-wrap */
420 Filters the messages of a translation catalog according to their attributes,\n\
421 and manipulates the attributes.\n"));
424 Mandatory arguments to long options are mandatory for short options too.\n"));
427 Input file location:\n"));
429 INPUTFILE input PO file\n"));
431 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
433 If no input file is given or if it is -, standard input is read.\n"));
436 Output file location:\n"));
438 -o, --output-file=FILE write output to specified file\n"));
440 The results are written to standard output if no output file is specified\n\
444 Message selection:\n"));
446 --translated keep translated, remove untranslated messages\n"));
448 --untranslated keep untranslated, remove translated messages\n"));
450 --no-fuzzy remove 'fuzzy' marked messages\n"));
452 --only-fuzzy keep 'fuzzy' marked messages\n"));
454 --no-obsolete remove obsolete #~ messages\n"));
456 --only-obsolete keep obsolete #~ messages\n"));
459 Attribute manipulation:\n"));
461 --set-fuzzy set all messages 'fuzzy'\n"));
463 --clear-fuzzy set all messages non-'fuzzy'\n"));
465 --set-obsolete set all messages obsolete\n"));
467 --clear-obsolete set all messages non-obsolete\n"));
469 --previous when setting 'fuzzy', keep previous msgids\n\
470 of translated messages.\n"));
472 --clear-previous remove the \"previous msgid\" from all messages\n"));
474 --only-file=FILE.po manipulate only entries listed in FILE.po\n"));
476 --ignore-file=FILE.po manipulate only entries not listed in FILE.po\n"));
478 --fuzzy synonym for --only-fuzzy --clear-fuzzy\n"));
480 --obsolete synonym for --only-obsolete --clear-obsolete\n"));
483 Input file syntax:\n"));
485 -P, --properties-input input file is in Java .properties syntax\n"));
487 --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n"));
490 Output details:\n"));
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"));
496 --style=STYLEFILE specify CSS style rule file for --color\n"));
498 -e, --no-escape do not use C escapes in output (default)\n"));
500 -E, --escape use C escapes in output, no extended chars\n"));
502 --force-po write PO file even if empty\n"));
504 -i, --indent write the .po file using indented style\n"));
506 --no-location do not write '#: filename:line' lines\n"));
508 -n, --add-location generate '#: filename:line' lines (default)\n"));
510 --strict write out strict Uniforum conforming .po file\n"));
512 -p, --properties-output write out a Java .properties file\n"));
514 --stringtable-output write out a NeXTstep/GNUstep .strings file\n"));
516 -w, --width=NUMBER set output page width\n"));
518 --no-wrap do not break long message lines, longer than\n\
519 the output page width, into several lines\n"));
521 -s, --sort-output generate sorted output\n"));
523 -F, --sort-by-file sort output by file location\n"));
526 Informative output:\n"));
528 -h, --help display this help and exit\n"));
530 -V, --version output version information and exit\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"),
544 /* Return true if a message should be kept. */
546 is_message_selected (const message_ty *mp)
548 /* Always keep the header entry. */
552 if ((to_remove & (REMOVE_UNTRANSLATED | REMOVE_TRANSLATED))
553 && (mp->msgstr[0] == '\0'
554 ? to_remove & REMOVE_UNTRANSLATED
555 : to_remove & REMOVE_TRANSLATED))
558 if ((to_remove & (REMOVE_FUZZY | REMOVE_NONFUZZY))
560 ? to_remove & REMOVE_FUZZY
561 : to_remove & REMOVE_NONFUZZY))
564 if ((to_remove & (REMOVE_OBSOLETE | REMOVE_NONOBSOLETE))
566 ? to_remove & REMOVE_OBSOLETE
567 : to_remove & REMOVE_NONOBSOLETE))
575 process_message_list (message_list_ty *mlp,
576 message_list_ty *only_mlp, message_list_ty *ignore_mlp)
578 /* Keep only the selected messages. */
579 message_list_remove_if_not (mlp, is_message_selected);
581 /* Change the attributes. */
586 for (j = 0; j < mlp->nitems; j++)
588 message_ty *mp = mlp->item[j];
590 /* Attribute changes only affect messages listed in --only-file
591 and not listed in --ignore-file. */
593 ? message_list_search (only_mlp, mp->msgctxt, mp->msgid) != NULL
596 ? message_list_search (ignore_mlp, mp->msgctxt, mp->msgid) == NULL
599 if (to_change & SET_FUZZY)
601 if ((to_change & ADD_PREV) && !is_header (mp)
602 && !mp->is_fuzzy && mp->msgstr[0] != '\0')
605 (mp->msgctxt != NULL ? xstrdup (mp->msgctxt) : NULL);
607 (mp->msgid != NULL ? xstrdup (mp->msgid) : NULL);
608 mp->prev_msgid_plural =
609 (mp->msgid_plural != NULL
610 ? xstrdup (mp->msgid_plural)
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))
621 if (to_change & RESET_OBSOLETE)
622 mp->obsolete = false;
623 if (to_change & REMOVE_PREV)
625 mp->prev_msgctxt = NULL;
626 mp->prev_msgid = NULL;
627 mp->prev_msgid_plural = NULL;
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)
642 for (k = 0; k < mdlp->nitems; k++)
643 process_message_list (mdlp->item[k]->messages,
645 ? msgdomain_list_sublist (only_mdlp,
646 mdlp->item[k]->domain,
650 ? msgdomain_list_sublist (ignore_mdlp,
651 mdlp->item[k]->domain,