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