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