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
main(int argc,char ** argv)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
usage(int status)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
is_message_selected(const message_ty * mp)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
process_message_list(message_list_ty * mlp,message_list_ty * only_mlp,message_list_ty * ignore_mlp)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 *
process_msgdomain_list(msgdomain_list_ty * mdlp,msgdomain_list_ty * only_mdlp,msgdomain_list_ty * ignore_mdlp)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