| /* Manipulates attributes of messages in translation catalogs. |
| Copyright (C) 2001-2007, 2009-2010, 2012-2014, 2016, 2018-2020 Free Software |
| Foundation, Inc. |
| Written by Bruno Haible <haible@clisp.cons.org>, 2001. |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <https://www.gnu.org/licenses/>. */ |
| |
| |
| #ifdef HAVE_CONFIG_H |
| # include "config.h" |
| #endif |
| |
| #include <getopt.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <locale.h> |
| |
| #include <textstyle.h> |
| |
| #include "noreturn.h" |
| #include "closeout.h" |
| #include "dir-list.h" |
| #include "error.h" |
| #include "error-progname.h" |
| #include "progname.h" |
| #include "relocatable.h" |
| #include "basename-lgpl.h" |
| #include "message.h" |
| #include "read-catalog.h" |
| #include "read-po.h" |
| #include "read-properties.h" |
| #include "read-stringtable.h" |
| #include "write-catalog.h" |
| #include "write-po.h" |
| #include "write-properties.h" |
| #include "write-stringtable.h" |
| #include "propername.h" |
| #include "xalloc.h" |
| #include "gettext.h" |
| |
| #define _(str) gettext (str) |
| |
| |
| /* Force output of PO file even if empty. */ |
| static int force_po; |
| |
| /* Bit mask of subsets to remove. */ |
| enum |
| { |
| REMOVE_UNTRANSLATED = 1 << 0, |
| REMOVE_TRANSLATED = 1 << 1, |
| REMOVE_FUZZY = 1 << 2, |
| REMOVE_NONFUZZY = 1 << 3, |
| REMOVE_OBSOLETE = 1 << 4, |
| REMOVE_NONOBSOLETE = 1 << 5 |
| }; |
| static int to_remove; |
| |
| /* Bit mask of actions to perform on all messages. */ |
| enum |
| { |
| SET_FUZZY = 1 << 0, |
| RESET_FUZZY = 1 << 1, |
| SET_OBSOLETE = 1 << 2, |
| RESET_OBSOLETE = 1 << 3, |
| REMOVE_PREV = 1 << 4, |
| ADD_PREV = 1 << 5, |
| REMOVE_TRANSLATION = 1 << 6 |
| }; |
| static int to_change; |
| |
| /* Long options. */ |
| static const struct option long_options[] = |
| { |
| { "add-location", optional_argument, NULL, 'n' }, |
| { "clear-fuzzy", no_argument, NULL, CHAR_MAX + 8 }, |
| { "clear-obsolete", no_argument, NULL, CHAR_MAX + 10 }, |
| { "clear-previous", no_argument, NULL, CHAR_MAX + 18 }, |
| { "empty", no_argument, NULL, CHAR_MAX + 23 }, |
| { "color", optional_argument, NULL, CHAR_MAX + 19 }, |
| { "directory", required_argument, NULL, 'D' }, |
| { "escape", no_argument, NULL, 'E' }, |
| { "force-po", no_argument, &force_po, 1 }, |
| { "fuzzy", no_argument, NULL, CHAR_MAX + 11 }, |
| { "help", no_argument, NULL, 'h' }, |
| { "ignore-file", required_argument, NULL, CHAR_MAX + 15 }, |
| { "indent", no_argument, NULL, 'i' }, |
| { "no-escape", no_argument, NULL, 'e' }, |
| { "no-fuzzy", no_argument, NULL, CHAR_MAX + 3 }, |
| { "no-location", no_argument, NULL, CHAR_MAX + 22 }, |
| { "no-obsolete", no_argument, NULL, CHAR_MAX + 5 }, |
| { "no-wrap", no_argument, NULL, CHAR_MAX + 13 }, |
| { "obsolete", no_argument, NULL, CHAR_MAX + 12 }, |
| { "only-file", required_argument, NULL, CHAR_MAX + 14 }, |
| { "only-fuzzy", no_argument, NULL, CHAR_MAX + 4 }, |
| { "only-obsolete", no_argument, NULL, CHAR_MAX + 6 }, |
| { "output-file", required_argument, NULL, 'o' }, |
| { "previous", no_argument, NULL, CHAR_MAX + 21 }, |
| { "properties-input", no_argument, NULL, 'P' }, |
| { "properties-output", no_argument, NULL, 'p' }, |
| { "set-fuzzy", no_argument, NULL, CHAR_MAX + 7 }, |
| { "set-obsolete", no_argument, NULL, CHAR_MAX + 9 }, |
| { "sort-by-file", no_argument, NULL, 'F' }, |
| { "sort-output", no_argument, NULL, 's' }, |
| { "stringtable-input", no_argument, NULL, CHAR_MAX + 16 }, |
| { "stringtable-output", no_argument, NULL, CHAR_MAX + 17 }, |
| { "strict", no_argument, NULL, 'S' }, |
| { "style", required_argument, NULL, CHAR_MAX + 20 }, |
| { "translated", no_argument, NULL, CHAR_MAX + 1 }, |
| { "untranslated", no_argument, NULL, CHAR_MAX + 2 }, |
| { "version", no_argument, NULL, 'V' }, |
| { "width", required_argument, NULL, 'w' }, |
| { NULL, 0, NULL, 0 } |
| }; |
| |
| |
| /* Forward declaration of local functions. */ |
| _GL_NORETURN_FUNC static void usage (int status); |
| static msgdomain_list_ty *process_msgdomain_list (msgdomain_list_ty *mdlp, |
| msgdomain_list_ty *only_mdlp, |
| msgdomain_list_ty *ignore_mdlp); |
| |
| |
| int |
| main (int argc, char **argv) |
| { |
| int optchar; |
| bool do_help; |
| bool do_version; |
| char *output_file; |
| const char *input_file; |
| const char *only_file; |
| const char *ignore_file; |
| msgdomain_list_ty *only_mdlp; |
| msgdomain_list_ty *ignore_mdlp; |
| msgdomain_list_ty *result; |
| catalog_input_format_ty input_syntax = &input_format_po; |
| catalog_output_format_ty output_syntax = &output_format_po; |
| bool sort_by_msgid = false; |
| bool sort_by_filepos = false; |
| |
| /* Set program name for messages. */ |
| set_program_name (argv[0]); |
| error_print_progname = maybe_print_progname; |
| |
| /* Set locale via LC_ALL. */ |
| setlocale (LC_ALL, ""); |
| |
| /* Set the text message domain. */ |
| bindtextdomain (PACKAGE, relocate (LOCALEDIR)); |
| bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR)); |
| textdomain (PACKAGE); |
| |
| /* Ensure that write errors on stdout are detected. */ |
| atexit (close_stdout); |
| |
| /* Set default values for variables. */ |
| do_help = false; |
| do_version = false; |
| output_file = NULL; |
| input_file = NULL; |
| only_file = NULL; |
| ignore_file = NULL; |
| |
| while ((optchar = getopt_long (argc, argv, "D:eEFhino:pPsVw:", long_options, |
| NULL)) != EOF) |
| switch (optchar) |
| { |
| case '\0': /* Long option. */ |
| break; |
| |
| case 'D': |
| dir_list_append (optarg); |
| break; |
| |
| case 'e': |
| message_print_style_escape (false); |
| break; |
| |
| case 'E': |
| message_print_style_escape (true); |
| break; |
| |
| case 'F': |
| sort_by_filepos = true; |
| break; |
| |
| case 'h': |
| do_help = true; |
| break; |
| |
| case 'i': |
| message_print_style_indent (); |
| break; |
| |
| case 'n': |
| if (handle_filepos_comment_option (optarg)) |
| usage (EXIT_FAILURE); |
| break; |
| |
| case 'o': |
| output_file = optarg; |
| break; |
| |
| case 'p': |
| output_syntax = &output_format_properties; |
| break; |
| |
| case 'P': |
| input_syntax = &input_format_properties; |
| break; |
| |
| case 's': |
| sort_by_msgid = true; |
| break; |
| |
| case 'S': |
| message_print_style_uniforum (); |
| break; |
| |
| case 'V': |
| do_version = true; |
| break; |
| |
| case 'w': |
| { |
| int value; |
| char *endp; |
| value = strtol (optarg, &endp, 10); |
| if (endp != optarg) |
| message_page_width_set (value); |
| } |
| break; |
| |
| case CHAR_MAX + 1: /* --translated */ |
| to_remove |= REMOVE_UNTRANSLATED; |
| break; |
| |
| case CHAR_MAX + 2: /* --untranslated */ |
| to_remove |= REMOVE_TRANSLATED; |
| break; |
| |
| case CHAR_MAX + 3: /* --no-fuzzy */ |
| to_remove |= REMOVE_FUZZY; |
| break; |
| |
| case CHAR_MAX + 4: /* --only-fuzzy */ |
| to_remove |= REMOVE_NONFUZZY; |
| break; |
| |
| case CHAR_MAX + 5: /* --no-obsolete */ |
| to_remove |= REMOVE_OBSOLETE; |
| break; |
| |
| case CHAR_MAX + 6: /* --only-obsolete */ |
| to_remove |= REMOVE_NONOBSOLETE; |
| break; |
| |
| case CHAR_MAX + 7: /* --set-fuzzy */ |
| to_change |= SET_FUZZY; |
| break; |
| |
| case CHAR_MAX + 8: /* --clear-fuzzy */ |
| to_change |= RESET_FUZZY; |
| break; |
| |
| case CHAR_MAX + 9: /* --set-obsolete */ |
| to_change |= SET_OBSOLETE; |
| break; |
| |
| case CHAR_MAX + 10: /* --clear-obsolete */ |
| to_change |= RESET_OBSOLETE; |
| break; |
| |
| case CHAR_MAX + 11: /* --fuzzy */ |
| to_remove |= REMOVE_NONFUZZY; |
| to_change |= RESET_FUZZY; |
| break; |
| |
| case CHAR_MAX + 12: /* --obsolete */ |
| to_remove |= REMOVE_NONOBSOLETE; |
| to_change |= RESET_OBSOLETE; |
| break; |
| |
| case CHAR_MAX + 13: /* --no-wrap */ |
| message_page_width_ignore (); |
| break; |
| |
| case CHAR_MAX + 14: /* --only-file */ |
| only_file = optarg; |
| break; |
| |
| case CHAR_MAX + 15: /* --ignore-file */ |
| ignore_file = optarg; |
| break; |
| |
| case CHAR_MAX + 16: /* --stringtable-input */ |
| input_syntax = &input_format_stringtable; |
| break; |
| |
| case CHAR_MAX + 17: /* --stringtable-output */ |
| output_syntax = &output_format_stringtable; |
| break; |
| |
| case CHAR_MAX + 18: /* --clear-previous */ |
| to_change |= REMOVE_PREV; |
| break; |
| |
| case CHAR_MAX + 19: /* --color */ |
| if (handle_color_option (optarg) || color_test_mode) |
| usage (EXIT_FAILURE); |
| break; |
| |
| case CHAR_MAX + 20: /* --style */ |
| handle_style_option (optarg); |
| break; |
| |
| case CHAR_MAX + 21: /* --previous */ |
| to_change |= ADD_PREV; |
| break; |
| |
| case CHAR_MAX + 22: /* --no-location */ |
| message_print_style_filepos (filepos_comment_none); |
| break; |
| |
| case CHAR_MAX + 23: /* --empty */ |
| to_change |= REMOVE_TRANSLATION; |
| break; |
| |
| default: |
| usage (EXIT_FAILURE); |
| /* NOTREACHED */ |
| } |
| |
| /* Version information requested. */ |
| if (do_version) |
| { |
| printf ("%s (GNU %s) %s\n", last_component (program_name), |
| PACKAGE, VERSION); |
| /* xgettext: no-wrap */ |
| printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ |
| License GPLv3+: GNU GPL version 3 or later <%s>\n\ |
| This is free software: you are free to change and redistribute it.\n\ |
| There is NO WARRANTY, to the extent permitted by law.\n\ |
| "), |
| "2001-2020", "https://gnu.org/licenses/gpl.html"); |
| printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); |
| exit (EXIT_SUCCESS); |
| } |
| |
| /* Help is requested. */ |
| if (do_help) |
| usage (EXIT_SUCCESS); |
| |
| /* Test whether we have an .po file name as argument. */ |
| if (optind == argc) |
| input_file = "-"; |
| else if (optind + 1 == argc) |
| input_file = argv[optind]; |
| else |
| { |
| error (EXIT_SUCCESS, 0, _("at most one input file allowed")); |
| usage (EXIT_FAILURE); |
| } |
| |
| /* Verify selected options. */ |
| if (sort_by_msgid && sort_by_filepos) |
| error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"), |
| "--sort-output", "--sort-by-file"); |
| |
| /* Read input file. */ |
| result = read_catalog_file (input_file, input_syntax); |
| |
| /* Read optional files that limit the extent of the attribute changes. */ |
| only_mdlp = (only_file != NULL |
| ? read_catalog_file (only_file, input_syntax) |
| : NULL); |
| ignore_mdlp = (ignore_file != NULL |
| ? read_catalog_file (ignore_file, input_syntax) |
| : NULL); |
| |
| /* Filter the messages and manipulate the attributes. */ |
| result = process_msgdomain_list (result, only_mdlp, ignore_mdlp); |
| |
| /* Sorting the list of messages. */ |
| if (sort_by_filepos) |
| msgdomain_list_sort_by_filepos (result); |
| else if (sort_by_msgid) |
| msgdomain_list_sort_by_msgid (result); |
| |
| /* Write the PO file. */ |
| msgdomain_list_print (result, output_file, output_syntax, force_po, false); |
| |
| exit (EXIT_SUCCESS); |
| } |
| |
| |
| /* Display usage information and exit. */ |
| static void |
| usage (int status) |
| { |
| if (status != EXIT_SUCCESS) |
| fprintf (stderr, _("Try '%s --help' for more information.\n"), |
| program_name); |
| else |
| { |
| printf (_("\ |
| Usage: %s [OPTION] [INPUTFILE]\n\ |
| "), program_name); |
| printf ("\n"); |
| /* xgettext: no-wrap */ |
| printf (_("\ |
| Filters the messages of a translation catalog according to their attributes,\n\ |
| and manipulates the attributes.\n")); |
| printf ("\n"); |
| printf (_("\ |
| Mandatory arguments to long options are mandatory for short options too.\n")); |
| printf ("\n"); |
| printf (_("\ |
| Input file location:\n")); |
| printf (_("\ |
| INPUTFILE input PO file\n")); |
| printf (_("\ |
| -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); |
| printf (_("\ |
| If no input file is given or if it is -, standard input is read.\n")); |
| printf ("\n"); |
| printf (_("\ |
| Output file location:\n")); |
| printf (_("\ |
| -o, --output-file=FILE write output to specified file\n")); |
| printf (_("\ |
| The results are written to standard output if no output file is specified\n\ |
| or if it is -.\n")); |
| printf ("\n"); |
| printf (_("\ |
| Message selection:\n")); |
| printf (_("\ |
| --translated keep translated, remove untranslated messages\n")); |
| printf (_("\ |
| --untranslated keep untranslated, remove translated messages\n")); |
| printf (_("\ |
| --no-fuzzy remove 'fuzzy' marked messages\n")); |
| printf (_("\ |
| --only-fuzzy keep 'fuzzy' marked messages\n")); |
| printf (_("\ |
| --no-obsolete remove obsolete #~ messages\n")); |
| printf (_("\ |
| --only-obsolete keep obsolete #~ messages\n")); |
| printf ("\n"); |
| printf (_("\ |
| Attribute manipulation:\n")); |
| printf (_("\ |
| --set-fuzzy set all messages 'fuzzy'\n")); |
| printf (_("\ |
| --clear-fuzzy set all messages non-'fuzzy'\n")); |
| printf (_("\ |
| --set-obsolete set all messages obsolete\n")); |
| printf (_("\ |
| --clear-obsolete set all messages non-obsolete\n")); |
| printf (_("\ |
| --previous when setting 'fuzzy', keep previous msgids\n\ |
| of translated messages.\n")); |
| printf (_("\ |
| --clear-previous remove the \"previous msgid\" from all messages\n")); |
| printf (_("\ |
| --empty when removing 'fuzzy', also set msgstr empty\n")); |
| printf (_("\ |
| --only-file=FILE.po manipulate only entries listed in FILE.po\n")); |
| printf (_("\ |
| --ignore-file=FILE.po manipulate only entries not listed in FILE.po\n")); |
| printf (_("\ |
| --fuzzy synonym for --only-fuzzy --clear-fuzzy\n")); |
| printf (_("\ |
| --obsolete synonym for --only-obsolete --clear-obsolete\n")); |
| printf ("\n"); |
| printf (_("\ |
| Input file syntax:\n")); |
| printf (_("\ |
| -P, --properties-input input file is in Java .properties syntax\n")); |
| printf (_("\ |
| --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n")); |
| printf ("\n"); |
| printf (_("\ |
| Output details:\n")); |
| printf (_("\ |
| --color use colors and other text attributes always\n\ |
| --color=WHEN use colors and other text attributes if WHEN.\n\ |
| WHEN may be 'always', 'never', 'auto', or 'html'.\n")); |
| printf (_("\ |
| --style=STYLEFILE specify CSS style rule file for --color\n")); |
| printf (_("\ |
| -e, --no-escape do not use C escapes in output (default)\n")); |
| printf (_("\ |
| -E, --escape use C escapes in output, no extended chars\n")); |
| printf (_("\ |
| --force-po write PO file even if empty\n")); |
| printf (_("\ |
| -i, --indent write the .po file using indented style\n")); |
| printf (_("\ |
| --no-location do not write '#: filename:line' lines\n")); |
| printf (_("\ |
| -n, --add-location generate '#: filename:line' lines (default)\n")); |
| printf (_("\ |
| --strict write out strict Uniforum conforming .po file\n")); |
| printf (_("\ |
| -p, --properties-output write out a Java .properties file\n")); |
| printf (_("\ |
| --stringtable-output write out a NeXTstep/GNUstep .strings file\n")); |
| printf (_("\ |
| -w, --width=NUMBER set output page width\n")); |
| printf (_("\ |
| --no-wrap do not break long message lines, longer than\n\ |
| the output page width, into several lines\n")); |
| printf (_("\ |
| -s, --sort-output generate sorted output\n")); |
| printf (_("\ |
| -F, --sort-by-file sort output by file location\n")); |
| printf ("\n"); |
| printf (_("\ |
| Informative output:\n")); |
| printf (_("\ |
| -h, --help display this help and exit\n")); |
| printf (_("\ |
| -V, --version output version information and exit\n")); |
| printf ("\n"); |
| /* TRANSLATORS: The first placeholder is the web address of the Savannah |
| project of this package. The second placeholder is the bug-reporting |
| email address for this package. Please add _another line_ saying |
| "Report translation bugs to <...>\n" with the address for translation |
| bugs (typically your translation team's web or email address). */ |
| printf(_("\ |
| Report bugs in the bug tracker at <%s>\n\ |
| or by email to <%s>.\n"), |
| "https://savannah.gnu.org/projects/gettext", |
| "bug-gettext@gnu.org"); |
| } |
| |
| exit (status); |
| } |
| |
| |
| /* Return true if a message should be kept. */ |
| static bool |
| is_message_selected (const message_ty *mp) |
| { |
| /* Always keep the header entry. */ |
| if (is_header (mp)) |
| return true; |
| |
| if ((to_remove & (REMOVE_UNTRANSLATED | REMOVE_TRANSLATED)) |
| && (mp->msgstr[0] == '\0' |
| ? to_remove & REMOVE_UNTRANSLATED |
| : to_remove & REMOVE_TRANSLATED)) |
| return false; |
| |
| if ((to_remove & (REMOVE_FUZZY | REMOVE_NONFUZZY)) |
| && (mp->is_fuzzy |
| ? to_remove & REMOVE_FUZZY |
| : to_remove & REMOVE_NONFUZZY)) |
| return false; |
| |
| if ((to_remove & (REMOVE_OBSOLETE | REMOVE_NONOBSOLETE)) |
| && (mp->obsolete |
| ? to_remove & REMOVE_OBSOLETE |
| : to_remove & REMOVE_NONOBSOLETE)) |
| return false; |
| |
| return true; |
| } |
| |
| |
| static void |
| process_message_list (message_list_ty *mlp, |
| message_list_ty *only_mlp, message_list_ty *ignore_mlp) |
| { |
| /* Keep only the selected messages. */ |
| message_list_remove_if_not (mlp, is_message_selected); |
| |
| /* Change the attributes. */ |
| if (to_change) |
| { |
| size_t j; |
| |
| for (j = 0; j < mlp->nitems; j++) |
| { |
| message_ty *mp = mlp->item[j]; |
| |
| /* Attribute changes only affect messages listed in --only-file |
| and not listed in --ignore-file. */ |
| if ((only_mlp |
| ? message_list_search (only_mlp, mp->msgctxt, mp->msgid) != NULL |
| : true) |
| && (ignore_mlp |
| ? message_list_search (ignore_mlp, mp->msgctxt, mp->msgid) == NULL |
| : true)) |
| { |
| if (to_change & SET_FUZZY) |
| { |
| if ((to_change & ADD_PREV) && !is_header (mp) |
| && !mp->is_fuzzy && mp->msgstr[0] != '\0') |
| { |
| mp->prev_msgctxt = |
| (mp->msgctxt != NULL ? xstrdup (mp->msgctxt) : NULL); |
| mp->prev_msgid = |
| (mp->msgid != NULL ? xstrdup (mp->msgid) : NULL); |
| mp->prev_msgid_plural = |
| (mp->msgid_plural != NULL |
| ? xstrdup (mp->msgid_plural) |
| : NULL); |
| } |
| mp->is_fuzzy = true; |
| } |
| |
| if (to_change & RESET_FUZZY) |
| { |
| if ((to_change & REMOVE_TRANSLATION) |
| && mp->is_fuzzy && !mp->obsolete) |
| { |
| unsigned long int nplurals = 0; |
| char *msgstr; |
| size_t pos; |
| |
| for (pos = 0; pos < mp->msgstr_len; ++pos) |
| if (!mp->msgstr[pos]) |
| ++nplurals; |
| free ((char *) mp->msgstr); |
| msgstr = XNMALLOC (nplurals, char); |
| memset (msgstr, '\0', nplurals); |
| mp->msgstr = msgstr; |
| mp->msgstr_len = nplurals; |
| } |
| mp->is_fuzzy = false; |
| } |
| /* Always keep the header entry non-obsolete. */ |
| if ((to_change & SET_OBSOLETE) && !is_header (mp)) |
| mp->obsolete = true; |
| if (to_change & RESET_OBSOLETE) |
| mp->obsolete = false; |
| if (to_change & REMOVE_PREV) |
| { |
| mp->prev_msgctxt = NULL; |
| mp->prev_msgid = NULL; |
| mp->prev_msgid_plural = NULL; |
| } |
| } |
| } |
| } |
| } |
| |
| |
| static msgdomain_list_ty * |
| process_msgdomain_list (msgdomain_list_ty *mdlp, |
| msgdomain_list_ty *only_mdlp, |
| msgdomain_list_ty *ignore_mdlp) |
| { |
| size_t k; |
| |
| for (k = 0; k < mdlp->nitems; k++) |
| process_message_list (mdlp->item[k]->messages, |
| only_mdlp |
| ? msgdomain_list_sublist (only_mdlp, |
| mdlp->item[k]->domain, |
| true) |
| : NULL, |
| ignore_mdlp |
| ? msgdomain_list_sublist (ignore_mdlp, |
| mdlp->item[k]->domain, |
| false) |
| : NULL); |
| |
| return mdlp; |
| } |