xref: /netbsd-src/external/gpl2/gettext/dist/gettext-tools/src/msgfmt.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
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