1 /* Converts Uniforum style .po files to binary .mo files
2 Copyright (C) 1995-1998, 2000-2006 Free Software Foundation, Inc.
3 Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
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 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 #include <ctype.h>
24 #include <getopt.h>
25 #include <limits.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <locale.h>
30
31 #include "closeout.h"
32 #include "dir-list.h"
33 #include "error.h"
34 #include "error-progname.h"
35 #include "progname.h"
36 #include "relocatable.h"
37 #include "basename.h"
38 #include "xerror.h"
39 #include "xvasprintf.h"
40 #include "xalloc.h"
41 #include "stpcpy.h"
42 #include "exit.h"
43 #include "msgfmt.h"
44 #include "write-mo.h"
45 #include "write-java.h"
46 #include "write-csharp.h"
47 #include "write-resources.h"
48 #include "write-tcl.h"
49 #include "write-qt.h"
50 #include "propername.h"
51 #include "message.h"
52 #include "open-catalog.h"
53 #include "read-catalog.h"
54 #include "read-po.h"
55 #include "read-properties.h"
56 #include "read-stringtable.h"
57 #include "po-charset.h"
58 #include "msgl-check.h"
59 #include "gettext.h"
60
61 #define _(str) gettext (str)
62
63 /* Contains exit status for case in which no premature exit occurs. */
64 static int exit_status;
65
66 /* If true include even fuzzy translations in output file. */
67 static bool include_fuzzies = false;
68
69 /* If true include even untranslated messages in output file. */
70 static bool include_untranslated = false;
71
72 /* Specifies name of the output file. */
73 static const char *output_file_name;
74
75 /* Java mode output file specification. */
76 static bool java_mode;
77 static bool assume_java2;
78 static const char *java_resource_name;
79 static const char *java_locale_name;
80 static const char *java_class_directory;
81
82 /* C# mode output file specification. */
83 static bool csharp_mode;
84 static const char *csharp_resource_name;
85 static const char *csharp_locale_name;
86 static const char *csharp_base_directory;
87
88 /* C# resources mode output file specification. */
89 static bool csharp_resources_mode;
90
91 /* Tcl mode output file specification. */
92 static bool tcl_mode;
93 static const char *tcl_locale_name;
94 static const char *tcl_base_directory;
95
96 /* Qt mode output file specification. */
97 static bool qt_mode;
98
99 /* We may have more than one input file. Domains with same names in
100 different files have to merged. So we need a list of tables for
101 each output file. */
102 struct msg_domain
103 {
104 /* List for mapping message IDs to message strings. */
105 message_list_ty *mlp;
106 /* Name of domain these ID/String pairs are part of. */
107 const char *domain_name;
108 /* Output file name. */
109 const char *file_name;
110 /* Link to the next domain. */
111 struct msg_domain *next;
112 };
113 static struct msg_domain *domain_list;
114 static struct msg_domain *current_domain;
115
116 /* Be more verbose. Use only 'fprintf' and 'multiline_warning' but not
117 'error' or 'multiline_error' to emit verbosity messages, because 'error'
118 and 'multiline_error' during PO file parsing cause the program to exit
119 with EXIT_FAILURE. See function lex_end(). */
120 bool verbose = false;
121
122 /* If true check strings according to format string rules for the
123 language. */
124 static bool check_format_strings = false;
125
126 /* If true check the header entry is present and complete. */
127 static bool check_header = false;
128
129 /* Check that domain directives can be satisfied. */
130 static bool check_domain = false;
131
132 /* Check that msgfmt's behaviour is semantically compatible with
133 X/Open msgfmt or XView msgfmt. */
134 static bool check_compatibility = false;
135
136 /* If true, consider that strings containing an '&' are menu items and
137 the '&' designates a keyboard accelerator, and verify that the translations
138 also have a keyboard accelerator. */
139 static bool check_accelerators = false;
140 static char accelerator_char = '&';
141
142 /* Counters for statistics on translations for the processed files. */
143 static int msgs_translated;
144 static int msgs_untranslated;
145 static int msgs_fuzzy;
146
147 /* If not zero print statistics about translation at the end. */
148 static int do_statistics;
149
150 /* Long options. */
151 static const struct option long_options[] =
152 {
153 { "alignment", required_argument, NULL, 'a' },
154 { "check", no_argument, NULL, 'c' },
155 { "check-accelerators", optional_argument, NULL, CHAR_MAX + 1 },
156 { "check-compatibility", no_argument, NULL, 'C' },
157 { "check-domain", no_argument, NULL, CHAR_MAX + 2 },
158 { "check-format", no_argument, NULL, CHAR_MAX + 3 },
159 { "check-header", no_argument, NULL, CHAR_MAX + 4 },
160 { "csharp", no_argument, NULL, CHAR_MAX + 10 },
161 { "csharp-resources", no_argument, NULL, CHAR_MAX + 11 },
162 { "directory", required_argument, NULL, 'D' },
163 { "endianness", required_argument, NULL, CHAR_MAX + 13 },
164 { "help", no_argument, NULL, 'h' },
165 { "java", no_argument, NULL, 'j' },
166 { "java2", no_argument, NULL, CHAR_MAX + 5 },
167 { "locale", required_argument, NULL, 'l' },
168 { "no-hash", no_argument, NULL, CHAR_MAX + 6 },
169 { "output-file", required_argument, NULL, 'o' },
170 { "properties-input", no_argument, NULL, 'P' },
171 { "qt", no_argument, NULL, CHAR_MAX + 9 },
172 { "resource", required_argument, NULL, 'r' },
173 { "statistics", no_argument, &do_statistics, 1 },
174 { "strict", no_argument, NULL, 'S' },
175 { "stringtable-input", no_argument, NULL, CHAR_MAX + 8 },
176 { "tcl", no_argument, NULL, CHAR_MAX + 7 },
177 { "use-fuzzy", no_argument, NULL, 'f' },
178 { "use-untranslated", no_argument, NULL, CHAR_MAX + 12 },
179 { "verbose", no_argument, NULL, 'v' },
180 { "version", no_argument, NULL, 'V' },
181 { NULL, 0, NULL, 0 }
182 };
183
184
185 /* Forward declaration of local functions. */
186 static void usage (int status)
187 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
188 __attribute__ ((noreturn))
189 #endif
190 ;
191 static const char *add_mo_suffix (const char *);
192 static struct msg_domain *new_domain (const char *name, const char *file_name);
193 static bool is_nonobsolete (const message_ty *mp);
194 static void read_catalog_file_msgfmt (char *filename,
195 catalog_input_format_ty input_syntax);
196
197
198 int
main(int argc,char * argv[])199 main (int argc, char *argv[])
200 {
201 int opt;
202 bool do_help = false;
203 bool do_version = false;
204 bool strict_uniforum = false;
205 catalog_input_format_ty input_syntax = &input_format_po;
206 const char *canon_encoding;
207 struct msg_domain *domain;
208
209 /* Set default value for global variables. */
210 alignment = DEFAULT_OUTPUT_ALIGNMENT;
211
212 /* Set program name for messages. */
213 set_program_name (argv[0]);
214 error_print_progname = maybe_print_progname;
215 error_one_per_line = 1;
216 exit_status = EXIT_SUCCESS;
217
218 #ifdef HAVE_SETLOCALE
219 /* Set locale via LC_ALL. */
220 setlocale (LC_ALL, "");
221 #endif
222
223 /* Set the text message domain. */
224 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
225 bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
226 textdomain (PACKAGE);
227
228 /* Ensure that write errors on stdout are detected. */
229 atexit (close_stdout);
230
231 while ((opt = getopt_long (argc, argv, "a:cCd:D:fhjl:o:Pr:vV", long_options,
232 NULL))
233 != EOF)
234 switch (opt)
235 {
236 case '\0': /* Long option. */
237 break;
238 case 'a':
239 {
240 char *endp;
241 size_t new_align = strtoul (optarg, &endp, 0);
242
243 if (endp != optarg)
244 alignment = new_align;
245 }
246 break;
247 case 'c':
248 check_domain = true;
249 check_format_strings = true;
250 check_header = true;
251 break;
252 case 'C':
253 check_compatibility = true;
254 break;
255 case 'd':
256 java_class_directory = optarg;
257 csharp_base_directory = optarg;
258 tcl_base_directory = optarg;
259 break;
260 case 'D':
261 dir_list_append (optarg);
262 break;
263 case 'f':
264 include_fuzzies = true;
265 break;
266 case 'h':
267 do_help = true;
268 break;
269 case 'j':
270 java_mode = true;
271 break;
272 case 'l':
273 java_locale_name = optarg;
274 csharp_locale_name = optarg;
275 tcl_locale_name = optarg;
276 break;
277 case 'o':
278 output_file_name = optarg;
279 break;
280 case 'P':
281 input_syntax = &input_format_properties;
282 break;
283 case 'r':
284 java_resource_name = optarg;
285 csharp_resource_name = optarg;
286 break;
287 case 'S':
288 strict_uniforum = true;
289 break;
290 case 'v':
291 verbose = true;
292 break;
293 case 'V':
294 do_version = true;
295 break;
296 case CHAR_MAX + 1: /* --check-accelerators */
297 check_accelerators = true;
298 if (optarg != NULL)
299 {
300 if (optarg[0] != '\0' && ispunct ((unsigned char) optarg[0])
301 && optarg[1] == '\0')
302 accelerator_char = optarg[0];
303 else
304 error (EXIT_FAILURE, 0,
305 _("the argument to %s should be a single punctuation character"),
306 "--check-accelerators");
307 }
308 break;
309 case CHAR_MAX + 2: /* --check-domain */
310 check_domain = true;
311 break;
312 case CHAR_MAX + 3: /* --check-format */
313 check_format_strings = true;
314 break;
315 case CHAR_MAX + 4: /* --check-header */
316 check_header = true;
317 break;
318 case CHAR_MAX + 5: /* --java2 */
319 java_mode = true;
320 assume_java2 = true;
321 break;
322 case CHAR_MAX + 6: /* --no-hash */
323 no_hash_table = true;
324 break;
325 case CHAR_MAX + 7: /* --tcl */
326 tcl_mode = true;
327 break;
328 case CHAR_MAX + 8: /* --stringtable-input */
329 input_syntax = &input_format_stringtable;
330 break;
331 case CHAR_MAX + 9: /* --qt */
332 qt_mode = true;
333 break;
334 case CHAR_MAX + 10: /* --csharp */
335 csharp_mode = true;
336 break;
337 case CHAR_MAX + 11: /* --csharp-resources */
338 csharp_resources_mode = true;
339 break;
340 case CHAR_MAX + 12: /* --use-untranslated (undocumented) */
341 include_untranslated = true;
342 break;
343 case CHAR_MAX + 13: /* --endianness={big|little} */
344 {
345 int endianness;
346
347 if (strcmp (optarg, "big") == 0)
348 endianness = 1;
349 else if (strcmp (optarg, "little") == 0)
350 endianness = 0;
351 else
352 error (EXIT_FAILURE, 0, _("invalid endianness: %s"), optarg);
353
354 byteswap = endianness ^ ENDIANNESS;
355 }
356 break;
357 default:
358 usage (EXIT_FAILURE);
359 break;
360 }
361
362 /* Version information is requested. */
363 if (do_version)
364 {
365 printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
366 /* xgettext: no-wrap */
367 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
368 This is free software; see the source for copying conditions. There is NO\n\
369 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
370 "),
371 "1995-1998, 2000-2006");
372 printf (_("Written by %s.\n"), proper_name ("Ulrich Drepper"));
373 exit (EXIT_SUCCESS);
374 }
375
376 /* Help is requested. */
377 if (do_help)
378 usage (EXIT_SUCCESS);
379
380 /* Test whether we have a .po file name as argument. */
381 if (optind >= argc)
382 {
383 error (EXIT_SUCCESS, 0, _("no input file given"));
384 usage (EXIT_FAILURE);
385 }
386
387 /* Check for contradicting options. */
388 {
389 unsigned int modes =
390 (java_mode ? 1 : 0)
391 | (csharp_mode ? 2 : 0)
392 | (csharp_resources_mode ? 4 : 0)
393 | (tcl_mode ? 8 : 0)
394 | (qt_mode ? 16 : 0);
395 static const char *mode_options[] =
396 { "--java", "--csharp", "--csharp-resources", "--tcl", "--qt" };
397 /* More than one bit set? */
398 if (modes & (modes - 1))
399 {
400 const char *first_option;
401 const char *second_option;
402 unsigned int i;
403 for (i = 0; ; i++)
404 if (modes & (1 << i))
405 break;
406 first_option = mode_options[i];
407 for (i = i + 1; ; i++)
408 if (modes & (1 << i))
409 break;
410 second_option = mode_options[i];
411 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
412 first_option, second_option);
413 }
414 }
415 if (java_mode)
416 {
417 if (output_file_name != NULL)
418 {
419 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
420 "--java", "--output-file");
421 }
422 if (java_class_directory == NULL)
423 {
424 error (EXIT_SUCCESS, 0,
425 _("%s requires a \"-d directory\" specification"),
426 "--java");
427 usage (EXIT_FAILURE);
428 }
429 }
430 else if (csharp_mode)
431 {
432 if (output_file_name != NULL)
433 {
434 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
435 "--csharp", "--output-file");
436 }
437 if (csharp_locale_name == NULL)
438 {
439 error (EXIT_SUCCESS, 0,
440 _("%s requires a \"-l locale\" specification"),
441 "--csharp");
442 usage (EXIT_FAILURE);
443 }
444 if (csharp_base_directory == NULL)
445 {
446 error (EXIT_SUCCESS, 0,
447 _("%s requires a \"-d directory\" specification"),
448 "--csharp");
449 usage (EXIT_FAILURE);
450 }
451 }
452 else if (tcl_mode)
453 {
454 if (output_file_name != NULL)
455 {
456 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
457 "--tcl", "--output-file");
458 }
459 if (tcl_locale_name == NULL)
460 {
461 error (EXIT_SUCCESS, 0,
462 _("%s requires a \"-l locale\" specification"),
463 "--tcl");
464 usage (EXIT_FAILURE);
465 }
466 if (tcl_base_directory == NULL)
467 {
468 error (EXIT_SUCCESS, 0,
469 _("%s requires a \"-d directory\" specification"),
470 "--tcl");
471 usage (EXIT_FAILURE);
472 }
473 }
474 else
475 {
476 if (java_resource_name != NULL)
477 {
478 error (EXIT_SUCCESS, 0, _("%s is only valid with %s or %s"),
479 "--resource", "--java", "--csharp");
480 usage (EXIT_FAILURE);
481 }
482 if (java_locale_name != NULL)
483 {
484 error (EXIT_SUCCESS, 0, _("%s is only valid with %s, %s or %s"),
485 "--locale", "--java", "--csharp", "--tcl");
486 usage (EXIT_FAILURE);
487 }
488 if (java_class_directory != NULL)
489 {
490 error (EXIT_SUCCESS, 0, _("%s is only valid with %s, %s or %s"),
491 "-d", "--java", "--csharp", "--tcl");
492 usage (EXIT_FAILURE);
493 }
494 }
495
496 /* The -o option determines the name of the domain and therefore
497 the output file. */
498 if (output_file_name != NULL)
499 current_domain =
500 new_domain (output_file_name,
501 strict_uniforum && !csharp_resources_mode && !qt_mode
502 ? add_mo_suffix (output_file_name)
503 : output_file_name);
504
505 /* Process all given .po files. */
506 while (argc > optind)
507 {
508 /* Remember that we currently have not specified any domain. This
509 is of course not true when we saw the -o option. */
510 if (output_file_name == NULL)
511 current_domain = NULL;
512
513 /* And process the input file. */
514 read_catalog_file_msgfmt (argv[optind], input_syntax);
515
516 ++optind;
517 }
518
519 /* We know a priori that some input_syntax->parse() functions convert
520 strings to UTF-8. */
521 canon_encoding = (input_syntax->produces_utf8 ? po_charset_utf8 : NULL);
522
523 /* Remove obsolete messages. They were only needed for duplicate
524 checking. */
525 for (domain = domain_list; domain != NULL; domain = domain->next)
526 message_list_remove_if_not (domain->mlp, is_nonobsolete);
527
528 /* Perform all kinds of checks: plural expressions, format strings, ... */
529 {
530 int nerrors = 0;
531
532 for (domain = domain_list; domain != NULL; domain = domain->next)
533 nerrors +=
534 check_message_list (domain->mlp,
535 1, check_format_strings, check_header,
536 check_compatibility,
537 check_accelerators, accelerator_char);
538
539 /* Exit with status 1 on any error. */
540 if (nerrors > 0)
541 {
542 error (0, 0,
543 ngettext ("found %d fatal error", "found %d fatal errors",
544 nerrors),
545 nerrors);
546 exit_status = EXIT_FAILURE;
547 }
548 }
549
550 /* Now write out all domains. */
551 for (domain = domain_list; domain != NULL; domain = domain->next)
552 {
553 if (java_mode)
554 {
555 if (msgdomain_write_java (domain->mlp, canon_encoding,
556 java_resource_name, java_locale_name,
557 java_class_directory, assume_java2))
558 exit_status = EXIT_FAILURE;
559 }
560 else if (csharp_mode)
561 {
562 if (msgdomain_write_csharp (domain->mlp, canon_encoding,
563 csharp_resource_name, csharp_locale_name,
564 csharp_base_directory))
565 exit_status = EXIT_FAILURE;
566 }
567 else if (csharp_resources_mode)
568 {
569 if (msgdomain_write_csharp_resources (domain->mlp, canon_encoding,
570 domain->domain_name,
571 domain->file_name))
572 exit_status = EXIT_FAILURE;
573 }
574 else if (tcl_mode)
575 {
576 if (msgdomain_write_tcl (domain->mlp, canon_encoding,
577 tcl_locale_name, tcl_base_directory))
578 exit_status = EXIT_FAILURE;
579 }
580 else if (qt_mode)
581 {
582 if (msgdomain_write_qt (domain->mlp, canon_encoding,
583 domain->domain_name, domain->file_name))
584 exit_status = EXIT_FAILURE;
585 }
586 else
587 {
588 if (msgdomain_write_mo (domain->mlp, domain->domain_name,
589 domain->file_name))
590 exit_status = EXIT_FAILURE;
591 }
592
593 /* List is not used anymore. */
594 message_list_free (domain->mlp, 0);
595 }
596
597 /* Print statistics if requested. */
598 if (verbose || do_statistics)
599 {
600 fprintf (stderr,
601 ngettext ("%d translated message", "%d translated messages",
602 msgs_translated),
603 msgs_translated);
604 if (msgs_fuzzy > 0)
605 fprintf (stderr,
606 ngettext (", %d fuzzy translation", ", %d fuzzy translations",
607 msgs_fuzzy),
608 msgs_fuzzy);
609 if (msgs_untranslated > 0)
610 fprintf (stderr,
611 ngettext (", %d untranslated message",
612 ", %d untranslated messages",
613 msgs_untranslated),
614 msgs_untranslated);
615 fputs (".\n", stderr);
616 }
617
618 exit (exit_status);
619 }
620
621
622 /* Display usage information and exit. */
623 static void
usage(int status)624 usage (int status)
625 {
626 if (status != EXIT_SUCCESS)
627 fprintf (stderr, _("Try `%s --help' for more information.\n"),
628 program_name);
629 else
630 {
631 printf (_("\
632 Usage: %s [OPTION] filename.po ...\n\
633 "), program_name);
634 printf ("\n");
635 printf (_("\
636 Generate binary message catalog from textual translation description.\n\
637 "));
638 printf ("\n");
639 /* xgettext: no-wrap */
640 printf (_("\
641 Mandatory arguments to long options are mandatory for short options too.\n\
642 Similarly for optional arguments.\n\
643 "));
644 printf ("\n");
645 printf (_("\
646 Input file location:\n"));
647 printf (_("\
648 filename.po ... input files\n"));
649 printf (_("\
650 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
651 printf (_("\
652 If input file is -, standard input is read.\n"));
653 printf ("\n");
654 printf (_("\
655 Operation mode:\n"));
656 printf (_("\
657 -j, --java Java mode: generate a Java ResourceBundle class\n"));
658 printf (_("\
659 --java2 like --java, and assume Java2 (JDK 1.2 or higher)\n"));
660 printf (_("\
661 --csharp C# mode: generate a .NET .dll file\n"));
662 printf (_("\
663 --csharp-resources C# resources mode: generate a .NET .resources file\n"));
664 printf (_("\
665 --tcl Tcl mode: generate a tcl/msgcat .msg file\n"));
666 printf (_("\
667 --qt Qt mode: generate a Qt .qm file\n"));
668 printf ("\n");
669 printf (_("\
670 Output file location:\n"));
671 printf (_("\
672 -o, --output-file=FILE write output to specified file\n"));
673 printf (_("\
674 --strict enable strict Uniforum mode\n"));
675 printf (_("\
676 If output file is -, output is written to standard output.\n"));
677 printf ("\n");
678 printf (_("\
679 Output file location in Java mode:\n"));
680 printf (_("\
681 -r, --resource=RESOURCE resource name\n"));
682 printf (_("\
683 -l, --locale=LOCALE locale name, either language or language_COUNTRY\n"));
684 printf (_("\
685 -d DIRECTORY base directory of classes directory hierarchy\n"));
686 printf (_("\
687 The class name is determined by appending the locale name to the resource name,\n\
688 separated with an underscore. The -d option is mandatory. The class is\n\
689 written under the specified directory.\n\
690 "));
691 printf ("\n");
692 printf (_("\
693 Output file location in C# mode:\n"));
694 printf (_("\
695 -r, --resource=RESOURCE resource name\n"));
696 printf (_("\
697 -l, --locale=LOCALE locale name, either language or language_COUNTRY\n"));
698 printf (_("\
699 -d DIRECTORY base directory for locale dependent .dll files\n"));
700 printf (_("\
701 The -l and -d options are mandatory. The .dll file is written in a\n\
702 subdirectory of the specified directory whose name depends on the locale.\n"));
703 printf ("\n");
704 printf (_("\
705 Output file location in Tcl mode:\n"));
706 printf (_("\
707 -l, --locale=LOCALE locale name, either language or language_COUNTRY\n"));
708 printf (_("\
709 -d DIRECTORY base directory of .msg message catalogs\n"));
710 printf (_("\
711 The -l and -d options are mandatory. The .msg file is written in the\n\
712 specified directory.\n"));
713 printf ("\n");
714 printf (_("\
715 Input file syntax:\n"));
716 printf (_("\
717 -P, --properties-input input files are in Java .properties syntax\n"));
718 printf (_("\
719 --stringtable-input input files are in NeXTstep/GNUstep .strings\n\
720 syntax\n"));
721 printf ("\n");
722 printf (_("\
723 Input file interpretation:\n"));
724 printf (_("\
725 -c, --check perform all the checks implied by\n\
726 --check-format, --check-header, --check-domain\n"));
727 printf (_("\
728 --check-format check language dependent format strings\n"));
729 printf (_("\
730 --check-header verify presence and contents of the header entry\n"));
731 printf (_("\
732 --check-domain check for conflicts between domain directives\n\
733 and the --output-file option\n"));
734 printf (_("\
735 -C, --check-compatibility check that GNU msgfmt behaves like X/Open msgfmt\n"));
736 printf (_("\
737 --check-accelerators[=CHAR] check presence of keyboard accelerators for\n\
738 menu items\n"));
739 printf (_("\
740 -f, --use-fuzzy use fuzzy entries in output\n"));
741 printf ("\n");
742 printf (_("\
743 Output details:\n"));
744 printf (_("\
745 -a, --alignment=NUMBER align strings to NUMBER bytes (default: %d)\n"), DEFAULT_OUTPUT_ALIGNMENT);
746 printf (_("\
747 --no-hash binary file will not include the hash table\n"));
748 printf ("\n");
749 printf (_("\
750 Informative output:\n"));
751 printf (_("\
752 -h, --help display this help and exit\n"));
753 printf (_("\
754 -V, --version output version information and exit\n"));
755 printf (_("\
756 --statistics print statistics about translations\n"));
757 printf (_("\
758 -v, --verbose increase verbosity level\n"));
759 printf ("\n");
760 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout);
761 }
762
763 exit (status);
764 }
765
766
767 static const char *
add_mo_suffix(const char * fname)768 add_mo_suffix (const char *fname)
769 {
770 size_t len;
771 char *result;
772
773 len = strlen (fname);
774 if (len > 3 && memcmp (fname + len - 3, ".mo", 3) == 0)
775 return fname;
776 if (len > 4 && memcmp (fname + len - 4, ".gmo", 4) == 0)
777 return fname;
778 result = (char *) xmalloc (len + 4);
779 stpcpy (stpcpy (result, fname), ".mo");
780 return result;
781 }
782
783
784 static struct msg_domain *
new_domain(const char * name,const char * file_name)785 new_domain (const char *name, const char *file_name)
786 {
787 struct msg_domain **p_dom = &domain_list;
788
789 while (*p_dom != NULL && strcmp (name, (*p_dom)->domain_name) != 0)
790 p_dom = &(*p_dom)->next;
791
792 if (*p_dom == NULL)
793 {
794 struct msg_domain *domain;
795
796 domain = (struct msg_domain *) xmalloc (sizeof (struct msg_domain));
797 domain->mlp = message_list_alloc (true);
798 domain->domain_name = name;
799 domain->file_name = file_name;
800 domain->next = NULL;
801 *p_dom = domain;
802 }
803
804 return *p_dom;
805 }
806
807
808 static bool
is_nonobsolete(const message_ty * mp)809 is_nonobsolete (const message_ty *mp)
810 {
811 return !mp->obsolete;
812 }
813
814
815 /* The rest of the file defines a subclass msgfmt_catalog_reader_ty of
816 default_catalog_reader_ty. Its particularities are:
817 - The header entry check is performed on-the-fly.
818 - Comments are not stored, they are discarded right away.
819 (This is achieved by setting handle_comments = false and
820 handle_filepos_comments = false.)
821 - The multi-domain handling is adapted to our domain_list.
822 */
823
824
825 /* This structure defines a derived class of the default_catalog_reader_ty
826 class. (See read-catalog-abstract.h for an explanation.) */
827 typedef struct msgfmt_catalog_reader_ty msgfmt_catalog_reader_ty;
828 struct msgfmt_catalog_reader_ty
829 {
830 /* inherited instance variables, etc */
831 DEFAULT_CATALOG_READER_TY
832
833 bool has_header_entry;
834 bool has_nonfuzzy_header_entry;
835 };
836
837
838 /* Prepare for first message. */
839 static void
msgfmt_constructor(abstract_catalog_reader_ty * that)840 msgfmt_constructor (abstract_catalog_reader_ty *that)
841 {
842 msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that;
843
844 /* Invoke superclass constructor. */
845 default_constructor (that);
846
847 this->has_header_entry = false;
848 this->has_nonfuzzy_header_entry = false;
849 }
850
851
852 /* Some checks after whole file is read. */
853 static void
msgfmt_parse_debrief(abstract_catalog_reader_ty * that)854 msgfmt_parse_debrief (abstract_catalog_reader_ty *that)
855 {
856 msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that;
857
858 /* Invoke superclass method. */
859 default_parse_debrief (that);
860
861 /* Test whether header entry was found. */
862 if (check_header)
863 {
864 if (!this->has_header_entry)
865 {
866 multiline_error (xasprintf ("%s: ", gram_pos.file_name),
867 xasprintf (_("\
868 warning: PO file header missing or invalid\n")));
869 multiline_error (NULL,
870 xasprintf (_("\
871 warning: charset conversion will not work\n")));
872 }
873 else if (!this->has_nonfuzzy_header_entry)
874 {
875 /* Has only a fuzzy header entry. Since the versions 0.10.xx
876 ignore a fuzzy header entry and even give an error on it, we
877 give a warning, to increase operability with these older
878 msgfmt versions. This warning can go away in January 2003. */
879 multiline_warning (xasprintf ("%s: ", gram_pos.file_name),
880 xasprintf (_("warning: PO file header fuzzy\n")));
881 multiline_warning (NULL,
882 xasprintf (_("\
883 warning: older versions of msgfmt will give an error on this\n")));
884 }
885 }
886 }
887
888
889 /* Set 'domain' directive when seen in .po file. */
890 static void
msgfmt_set_domain(default_catalog_reader_ty * this,char * name)891 msgfmt_set_domain (default_catalog_reader_ty *this, char *name)
892 {
893 /* If no output file was given, we change it with each `domain'
894 directive. */
895 if (!java_mode && !csharp_mode && !csharp_resources_mode && !tcl_mode
896 && !qt_mode && output_file_name == NULL)
897 {
898 size_t correct;
899
900 correct = strcspn (name, INVALID_PATH_CHAR);
901 if (name[correct] != '\0')
902 {
903 exit_status = EXIT_FAILURE;
904 if (correct == 0)
905 {
906 error (0, 0, _("\
907 domain name \"%s\" not suitable as file name"), name);
908 return;
909 }
910 else
911 error (0, 0, _("\
912 domain name \"%s\" not suitable as file name: will use prefix"), name);
913 name[correct] = '\0';
914 }
915
916 /* Set new domain. */
917 current_domain = new_domain (name, add_mo_suffix (name));
918 this->domain = current_domain->domain_name;
919 this->mlp = current_domain->mlp;
920 }
921 else
922 {
923 if (check_domain)
924 po_gram_error_at_line (&gram_pos,
925 _("`domain %s' directive ignored"), name);
926
927 /* NAME was allocated in po-gram-gen.y but is not used anywhere. */
928 free (name);
929 }
930 }
931
932
933 static void
msgfmt_add_message(default_catalog_reader_ty * this,char * msgctxt,char * msgid,lex_pos_ty * msgid_pos,char * msgid_plural,char * msgstr,size_t msgstr_len,lex_pos_ty * msgstr_pos,char * prev_msgctxt,char * prev_msgid,char * prev_msgid_plural,bool force_fuzzy,bool obsolete)934 msgfmt_add_message (default_catalog_reader_ty *this,
935 char *msgctxt,
936 char *msgid,
937 lex_pos_ty *msgid_pos,
938 char *msgid_plural,
939 char *msgstr, size_t msgstr_len,
940 lex_pos_ty *msgstr_pos,
941 char *prev_msgctxt,
942 char *prev_msgid,
943 char *prev_msgid_plural,
944 bool force_fuzzy, bool obsolete)
945 {
946 /* Check whether already a domain is specified. If not, use default
947 domain. */
948 if (current_domain == NULL)
949 {
950 current_domain = new_domain (MESSAGE_DOMAIN_DEFAULT,
951 add_mo_suffix (MESSAGE_DOMAIN_DEFAULT));
952 /* Keep current_domain and this->domain synchronized. */
953 this->domain = current_domain->domain_name;
954 this->mlp = current_domain->mlp;
955 }
956
957 /* Invoke superclass method. */
958 default_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural,
959 msgstr, msgstr_len, msgstr_pos,
960 prev_msgctxt, prev_msgid, prev_msgid_plural,
961 force_fuzzy, obsolete);
962 }
963
964
965 static void
msgfmt_frob_new_message(default_catalog_reader_ty * that,message_ty * mp,const lex_pos_ty * msgid_pos,const lex_pos_ty * msgstr_pos)966 msgfmt_frob_new_message (default_catalog_reader_ty *that, message_ty *mp,
967 const lex_pos_ty *msgid_pos,
968 const lex_pos_ty *msgstr_pos)
969 {
970 msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that;
971
972 if (!mp->obsolete)
973 {
974 /* Don't emit untranslated entries.
975 Also don't emit fuzzy entries, unless --use-fuzzy was specified.
976 But ignore fuzziness of the header entry. */
977 if ((!include_untranslated && mp->msgstr[0] == '\0')
978 || (!include_fuzzies && mp->is_fuzzy && !is_header (mp)))
979 {
980 if (check_compatibility)
981 {
982 error_with_progname = false;
983 error_at_line (0, 0, mp->pos.file_name, mp->pos.line_number,
984 (mp->msgstr[0] == '\0'
985 ? _("empty `msgstr' entry ignored")
986 : _("fuzzy `msgstr' entry ignored")));
987 error_with_progname = true;
988 }
989
990 /* Increment counter for fuzzy/untranslated messages. */
991 if (mp->msgstr[0] == '\0')
992 ++msgs_untranslated;
993 else
994 ++msgs_fuzzy;
995
996 mp->obsolete = true;
997 }
998 else
999 {
1000 /* Test for header entry. */
1001 if (is_header (mp))
1002 {
1003 this->has_header_entry = true;
1004 if (!mp->is_fuzzy)
1005 this->has_nonfuzzy_header_entry = true;
1006 }
1007 else
1008 /* We don't count the header entry in the statistic so place
1009 the counter incrementation here. */
1010 if (mp->is_fuzzy)
1011 ++msgs_fuzzy;
1012 else
1013 ++msgs_translated;
1014 }
1015 }
1016 }
1017
1018
1019 /* Test for `#, fuzzy' comments and warn. */
1020 static void
msgfmt_comment_special(abstract_catalog_reader_ty * that,const char * s)1021 msgfmt_comment_special (abstract_catalog_reader_ty *that, const char *s)
1022 {
1023 msgfmt_catalog_reader_ty *this = (msgfmt_catalog_reader_ty *) that;
1024
1025 /* Invoke superclass method. */
1026 default_comment_special (that, s);
1027
1028 if (this->is_fuzzy)
1029 {
1030 static bool warned = false;
1031
1032 if (!include_fuzzies && check_compatibility && !warned)
1033 {
1034 warned = true;
1035 error (0, 0, _("\
1036 %s: warning: source file contains fuzzy translation"),
1037 gram_pos.file_name);
1038 }
1039 }
1040 }
1041
1042
1043 /* So that the one parser can be used for multiple programs, and also
1044 use good data hiding and encapsulation practices, an object
1045 oriented approach has been taken. An object instance is allocated,
1046 and all actions resulting from the parse will be through
1047 invocations of method functions of that object. */
1048
1049 static default_catalog_reader_class_ty msgfmt_methods =
1050 {
1051 {
1052 sizeof (msgfmt_catalog_reader_ty),
1053 msgfmt_constructor,
1054 default_destructor,
1055 default_parse_brief,
1056 msgfmt_parse_debrief,
1057 default_directive_domain,
1058 default_directive_message,
1059 default_comment,
1060 default_comment_dot,
1061 default_comment_filepos,
1062 msgfmt_comment_special
1063 },
1064 msgfmt_set_domain, /* set_domain */
1065 msgfmt_add_message, /* add_message */
1066 msgfmt_frob_new_message /* frob_new_message */
1067 };
1068
1069
1070 /* Read .po file FILENAME and store translation pairs. */
1071 static void
read_catalog_file_msgfmt(char * filename,catalog_input_format_ty input_syntax)1072 read_catalog_file_msgfmt (char *filename, catalog_input_format_ty input_syntax)
1073 {
1074 char *real_filename;
1075 FILE *fp = open_catalog_file (filename, &real_filename, true);
1076 default_catalog_reader_ty *pop;
1077
1078 pop = default_catalog_reader_alloc (&msgfmt_methods);
1079 pop->handle_comments = false;
1080 pop->handle_filepos_comments = false;
1081 pop->allow_domain_directives = true;
1082 pop->allow_duplicates = false;
1083 pop->allow_duplicates_if_same_msgstr = false;
1084 pop->mdlp = NULL;
1085 pop->mlp = NULL;
1086 if (current_domain != NULL)
1087 {
1088 /* Keep current_domain and this->domain synchronized. */
1089 pop->domain = current_domain->domain_name;
1090 pop->mlp = current_domain->mlp;
1091 }
1092 po_lex_pass_obsolete_entries (true);
1093 catalog_reader_parse ((abstract_catalog_reader_ty *) pop, fp, real_filename,
1094 filename, input_syntax);
1095 catalog_reader_free ((abstract_catalog_reader_ty *) pop);
1096
1097 if (fp != stdin)
1098 fclose (fp);
1099 }
1100