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 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 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 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 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 * 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