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