No empty .Rs/.Re
[netbsd-mini2440.git] / gnu / dist / gettext / gettext-tools / src / msgfmt.c
blobf7bd56e63123133c89d1ae3e2c7256f70b170530
1 /* Converts Uniforum style .po files to binary .mo files
2 Copyright (C) 1995-1998, 2000-2005 Free Software Foundation, Inc.
3 Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
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.
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.
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
23 #include <ctype.h>
24 #include <getopt.h>
25 #include <limits.h>
26 #include <setjmp.h>
27 #include <signal.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <stdarg.h>
32 #include <locale.h>
34 #include "closeout.h"
35 #include "dir-list.h"
36 #include "error.h"
37 #include "error-progname.h"
38 #include "progname.h"
39 #include "relocatable.h"
40 #include "basename.h"
41 #include "xerror.h"
42 #include "format.h"
43 #include "xalloc.h"
44 #include "plural-exp.h"
45 #include "plural-table.h"
46 #include "strstr.h"
47 #include "stpcpy.h"
48 #include "exit.h"
49 #include "msgfmt.h"
50 #include "write-mo.h"
51 #include "write-java.h"
52 #include "write-csharp.h"
53 #include "write-resources.h"
54 #include "write-tcl.h"
55 #include "write-qt.h"
57 #include "gettext.h"
58 #include "message.h"
59 #include "open-po.h"
60 #include "read-po.h"
61 #include "po-charset.h"
63 #define _(str) gettext (str)
65 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
67 /* Some platforms don't have the sigjmp_buf type in <setjmp.h>. */
68 #if defined _MSC_VER || defined __MINGW32__
69 /* Native Woe32 API. */
70 # define sigjmp_buf jmp_buf
71 # define sigsetjmp(env,savesigs) setjmp (env)
72 # define siglongjmp longjmp
73 #endif
75 /* We use siginfo to get precise information about the signal.
76 But siginfo doesn't work on Irix 6.5. */
77 #if HAVE_SIGINFO && !defined (__sgi)
78 # define USE_SIGINFO 1
79 #endif
81 /* Contains exit status for case in which no premature exit occurs. */
82 static int exit_status;
84 /* If true include even fuzzy translations in output file. */
85 static bool include_all = false;
87 /* Specifies name of the output file. */
88 static const char *output_file_name;
90 /* Java mode output file specification. */
91 static bool java_mode;
92 static bool assume_java2;
93 static const char *java_resource_name;
94 static const char *java_locale_name;
95 static const char *java_class_directory;
97 /* C# mode output file specification. */
98 static bool csharp_mode;
99 static const char *csharp_resource_name;
100 static const char *csharp_locale_name;
101 static const char *csharp_base_directory;
103 /* C# resources mode output file specification. */
104 static bool csharp_resources_mode;
106 /* Tcl mode output file specification. */
107 static bool tcl_mode;
108 static const char *tcl_locale_name;
109 static const char *tcl_base_directory;
111 /* Qt mode output file specification. */
112 static bool qt_mode;
114 /* We may have more than one input file. Domains with same names in
115 different files have to merged. So we need a list of tables for
116 each output file. */
117 struct msg_domain
119 /* List for mapping message IDs to message strings. */
120 message_list_ty *mlp;
121 /* Name of domain these ID/String pairs are part of. */
122 const char *domain_name;
123 /* Output file name. */
124 const char *file_name;
125 /* Link to the next domain. */
126 struct msg_domain *next;
128 static struct msg_domain *domain_list;
129 static struct msg_domain *current_domain;
131 /* Be more verbose. Use only 'fprintf' and 'multiline_warning' but not
132 'error' or 'multiline_error' to emit verbosity messages, because 'error'
133 and 'multiline_error' during PO file parsing cause the program to exit
134 with EXIT_FAILURE. See function lex_end(). */
135 bool verbose = false;
137 /* If true check strings according to format string rules for the
138 language. */
139 static bool check_format_strings = false;
141 /* If true check the header entry is present and complete. */
142 static bool check_header = false;
144 /* Check that domain directives can be satisfied. */
145 static bool check_domain = false;
147 /* Check that msgfmt's behaviour is semantically compatible with
148 X/Open msgfmt or XView msgfmt. */
149 static bool check_compatibility = false;
151 /* If true, consider that strings containing an '&' are menu items and
152 the '&' designates a keyboard accelerator, and verify that the translations
153 also have a keyboard accelerator. */
154 static bool check_accelerators = false;
155 static char accelerator_char = '&';
157 /* Counters for statistics on translations for the processed files. */
158 static int msgs_translated;
159 static int msgs_untranslated;
160 static int msgs_fuzzy;
162 /* If not zero print statistics about translation at the end. */
163 static int do_statistics;
165 /* Long options. */
166 static const struct option long_options[] =
168 { "alignment", required_argument, NULL, 'a' },
169 { "check", no_argument, NULL, 'c' },
170 { "check-accelerators", optional_argument, NULL, CHAR_MAX + 1 },
171 { "check-compatibility", no_argument, NULL, 'C' },
172 { "check-domain", no_argument, NULL, CHAR_MAX + 2 },
173 { "check-format", no_argument, NULL, CHAR_MAX + 3 },
174 { "check-header", no_argument, NULL, CHAR_MAX + 4 },
175 { "csharp", no_argument, NULL, CHAR_MAX + 10 },
176 { "csharp-resources", no_argument, NULL, CHAR_MAX + 11 },
177 { "directory", required_argument, NULL, 'D' },
178 { "help", no_argument, NULL, 'h' },
179 { "java", no_argument, NULL, 'j' },
180 { "java2", no_argument, NULL, CHAR_MAX + 5 },
181 { "locale", required_argument, NULL, 'l' },
182 { "no-hash", no_argument, NULL, CHAR_MAX + 6 },
183 { "output-file", required_argument, NULL, 'o' },
184 { "properties-input", no_argument, NULL, 'P' },
185 { "qt", no_argument, NULL, CHAR_MAX + 9 },
186 { "resource", required_argument, NULL, 'r' },
187 { "statistics", no_argument, &do_statistics, 1 },
188 { "strict", no_argument, NULL, 'S' },
189 { "stringtable-input", no_argument, NULL, CHAR_MAX + 8 },
190 { "tcl", no_argument, NULL, CHAR_MAX + 7 },
191 { "use-fuzzy", no_argument, NULL, 'f' },
192 { "verbose", no_argument, NULL, 'v' },
193 { "version", no_argument, NULL, 'V' },
194 { NULL, 0, NULL, 0 }
198 /* Forward declaration of local functions. */
199 static void usage (int status)
200 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
201 __attribute__ ((noreturn))
202 #endif
204 static const char *add_mo_suffix (const char *);
205 static struct msg_domain *new_domain (const char *name, const char *file_name);
206 static bool is_nonobsolete (const message_ty *mp);
207 static void check_plural (message_list_ty *mlp);
208 static void read_po_file_msgfmt (char *filename);
212 main (int argc, char *argv[])
214 int opt;
215 bool do_help = false;
216 bool do_version = false;
217 bool strict_uniforum = false;
218 const char *canon_encoding;
219 struct msg_domain *domain;
221 /* Set default value for global variables. */
222 alignment = DEFAULT_OUTPUT_ALIGNMENT;
224 /* Set program name for messages. */
225 set_program_name (argv[0]);
226 error_print_progname = maybe_print_progname;
227 error_one_per_line = 1;
228 exit_status = EXIT_SUCCESS;
230 #ifdef HAVE_SETLOCALE
231 /* Set locale via LC_ALL. */
232 setlocale (LC_ALL, "");
233 #endif
235 /* Set the text message domain. */
236 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
237 textdomain (PACKAGE);
239 /* Ensure that write errors on stdout are detected. */
240 atexit (close_stdout);
242 while ((opt = getopt_long (argc, argv, "a:cCd:D:fhjl:o:Pr:vV", long_options,
243 NULL))
244 != EOF)
245 switch (opt)
247 case '\0': /* Long option. */
248 break;
249 case 'a':
251 char *endp;
252 size_t new_align = strtoul (optarg, &endp, 0);
254 if (endp != optarg)
255 alignment = new_align;
257 break;
258 case 'c':
259 check_domain = true;
260 check_format_strings = true;
261 check_header = true;
262 break;
263 case 'C':
264 check_compatibility = true;
265 break;
266 case 'd':
267 java_class_directory = optarg;
268 csharp_base_directory = optarg;
269 tcl_base_directory = optarg;
270 break;
271 case 'D':
272 dir_list_append (optarg);
273 break;
274 case 'f':
275 include_all = true;
276 break;
277 case 'h':
278 do_help = true;
279 break;
280 case 'j':
281 java_mode = true;
282 break;
283 case 'l':
284 java_locale_name = optarg;
285 csharp_locale_name = optarg;
286 tcl_locale_name = optarg;
287 break;
288 case 'o':
289 output_file_name = optarg;
290 break;
291 case 'P':
292 input_syntax = syntax_properties;
293 break;
294 case 'r':
295 java_resource_name = optarg;
296 csharp_resource_name = optarg;
297 break;
298 case 'S':
299 strict_uniforum = true;
300 break;
301 case 'v':
302 verbose = true;
303 break;
304 case 'V':
305 do_version = true;
306 break;
307 case CHAR_MAX + 1: /* --check-accelerators */
308 check_accelerators = true;
309 if (optarg != NULL)
311 if (optarg[0] != '\0' && ispunct ((unsigned char) optarg[0])
312 && optarg[1] == '\0')
313 accelerator_char = optarg[0];
314 else
315 error (EXIT_FAILURE, 0,
316 _("the argument to %s should be a single punctuation character"),
317 "--check-accelerators");
319 break;
320 case CHAR_MAX + 2: /* --check-domain */
321 check_domain = true;
322 break;
323 case CHAR_MAX + 3: /* --check-format */
324 check_format_strings = true;
325 break;
326 case CHAR_MAX + 4: /* --check-header */
327 check_header = true;
328 break;
329 case CHAR_MAX + 5: /* --java2 */
330 java_mode = true;
331 assume_java2 = true;
332 break;
333 case CHAR_MAX + 6: /* --no-hash */
334 no_hash_table = true;
335 break;
336 case CHAR_MAX + 7: /* --tcl */
337 tcl_mode = true;
338 break;
339 case CHAR_MAX + 8: /* --stringtable-input */
340 input_syntax = syntax_stringtable;
341 break;
342 case CHAR_MAX + 9: /* --qt */
343 qt_mode = true;
344 break;
345 case CHAR_MAX + 10: /* --csharp */
346 csharp_mode = true;
347 break;
348 case CHAR_MAX + 11: /* --csharp-resources */
349 csharp_resources_mode = true;
350 break;
351 default:
352 usage (EXIT_FAILURE);
353 break;
356 /* Version information is requested. */
357 if (do_version)
359 printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
360 /* xgettext: no-wrap */
361 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
362 This is free software; see the source for copying conditions. There is NO\n\
363 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
365 "1995-1998, 2000-2005");
366 printf (_("Written by %s.\n"), "Ulrich Drepper");
367 exit (EXIT_SUCCESS);
370 /* Help is requested. */
371 if (do_help)
372 usage (EXIT_SUCCESS);
374 /* Test whether we have a .po file name as argument. */
375 if (optind >= argc)
377 error (EXIT_SUCCESS, 0, _("no input file given"));
378 usage (EXIT_FAILURE);
381 /* Check for contradicting options. */
383 unsigned int modes =
384 (java_mode ? 1 : 0)
385 | (csharp_mode ? 2 : 0)
386 | (csharp_resources_mode ? 4 : 0)
387 | (tcl_mode ? 8 : 0)
388 | (qt_mode ? 16 : 0);
389 static const char *mode_options[] =
390 { "--java", "--csharp", "--csharp-resources", "--tcl", "--qt" };
391 /* More than one bit set? */
392 if (modes & (modes - 1))
394 const char *first_option;
395 const char *second_option;
396 unsigned int i;
397 for (i = 0; ; i++)
398 if (modes & (1 << i))
399 break;
400 first_option = mode_options[i];
401 for (i = i + 1; ; i++)
402 if (modes & (1 << i))
403 break;
404 second_option = mode_options[i];
405 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
406 first_option, second_option);
409 if (java_mode)
411 if (output_file_name != NULL)
413 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
414 "--java", "--output-file");
416 if (java_class_directory == NULL)
418 error (EXIT_SUCCESS, 0,
419 _("%s requires a \"-d directory\" specification"),
420 "--java");
421 usage (EXIT_FAILURE);
424 else if (csharp_mode)
426 if (output_file_name != NULL)
428 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
429 "--csharp", "--output-file");
431 if (csharp_locale_name == NULL)
433 error (EXIT_SUCCESS, 0,
434 _("%s requires a \"-l locale\" specification"),
435 "--csharp");
436 usage (EXIT_FAILURE);
438 if (csharp_base_directory == NULL)
440 error (EXIT_SUCCESS, 0,
441 _("%s requires a \"-d directory\" specification"),
442 "--csharp");
443 usage (EXIT_FAILURE);
446 else if (tcl_mode)
448 if (output_file_name != NULL)
450 error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
451 "--tcl", "--output-file");
453 if (tcl_locale_name == NULL)
455 error (EXIT_SUCCESS, 0,
456 _("%s requires a \"-l locale\" specification"),
457 "--tcl");
458 usage (EXIT_FAILURE);
460 if (tcl_base_directory == NULL)
462 error (EXIT_SUCCESS, 0,
463 _("%s requires a \"-d directory\" specification"),
464 "--tcl");
465 usage (EXIT_FAILURE);
468 else
470 if (java_resource_name != NULL)
472 error (EXIT_SUCCESS, 0, _("%s is only valid with %s or %s"),
473 "--resource", "--java", "--csharp");
474 usage (EXIT_FAILURE);
476 if (java_locale_name != NULL)
478 error (EXIT_SUCCESS, 0, _("%s is only valid with %s, %s or %s"),
479 "--locale", "--java", "--csharp", "--tcl");
480 usage (EXIT_FAILURE);
482 if (java_class_directory != NULL)
484 error (EXIT_SUCCESS, 0, _("%s is only valid with %s, %s or %s"),
485 "-d", "--java", "--csharp", "--tcl");
486 usage (EXIT_FAILURE);
490 /* The -o option determines the name of the domain and therefore
491 the output file. */
492 if (output_file_name != NULL)
493 current_domain =
494 new_domain (output_file_name,
495 strict_uniforum && !csharp_resources_mode && !qt_mode
496 ? add_mo_suffix (output_file_name)
497 : output_file_name);
499 /* Process all given .po files. */
500 while (argc > optind)
502 /* Remember that we currently have not specified any domain. This
503 is of course not true when we saw the -o option. */
504 if (output_file_name == NULL)
505 current_domain = NULL;
507 /* And process the input file. */
508 read_po_file_msgfmt (argv[optind]);
510 ++optind;
513 /* We know a priori that properties_parse() and stringtable_parse() convert
514 strings to UTF-8. */
515 canon_encoding =
516 (input_syntax == syntax_properties || input_syntax == syntax_stringtable
517 ? po_charset_utf8
518 : NULL);
520 /* Remove obsolete messages. They were only needed for duplicate
521 checking. */
522 for (domain = domain_list; domain != NULL; domain = domain->next)
523 message_list_remove_if_not (domain->mlp, is_nonobsolete);
525 /* Check the plural expression is present if needed and has valid syntax. */
526 if (check_header)
527 for (domain = domain_list; domain != NULL; domain = domain->next)
528 check_plural (domain->mlp);
530 /* Now write out all domains. */
531 for (domain = domain_list; domain != NULL; domain = domain->next)
533 if (java_mode)
535 if (msgdomain_write_java (domain->mlp, canon_encoding,
536 java_resource_name, java_locale_name,
537 java_class_directory, assume_java2))
538 exit_status = EXIT_FAILURE;
540 else if (csharp_mode)
542 if (msgdomain_write_csharp (domain->mlp, canon_encoding,
543 csharp_resource_name, csharp_locale_name,
544 csharp_base_directory))
545 exit_status = EXIT_FAILURE;
547 else if (csharp_resources_mode)
549 if (msgdomain_write_csharp_resources (domain->mlp, canon_encoding,
550 domain->domain_name,
551 domain->file_name))
552 exit_status = EXIT_FAILURE;
554 else if (tcl_mode)
556 if (msgdomain_write_tcl (domain->mlp, canon_encoding,
557 tcl_locale_name, tcl_base_directory))
558 exit_status = EXIT_FAILURE;
560 else if (qt_mode)
562 if (msgdomain_write_qt (domain->mlp, canon_encoding,
563 domain->domain_name, domain->file_name))
564 exit_status = EXIT_FAILURE;
566 else
568 if (msgdomain_write_mo (domain->mlp, domain->domain_name,
569 domain->file_name))
570 exit_status = EXIT_FAILURE;
573 /* List is not used anymore. */
574 message_list_free (domain->mlp);
577 /* Print statistics if requested. */
578 if (verbose || do_statistics)
580 fprintf (stderr,
581 ngettext ("%d translated message", "%d translated messages",
582 msgs_translated),
583 msgs_translated);
584 if (msgs_fuzzy > 0)
585 fprintf (stderr,
586 ngettext (", %d fuzzy translation", ", %d fuzzy translations",
587 msgs_fuzzy),
588 msgs_fuzzy);
589 if (msgs_untranslated > 0)
590 fprintf (stderr,
591 ngettext (", %d untranslated message",
592 ", %d untranslated messages",
593 msgs_untranslated),
594 msgs_untranslated);
595 fputs (".\n", stderr);
598 exit (exit_status);
602 /* Display usage information and exit. */
603 static void
604 usage (int status)
606 if (status != EXIT_SUCCESS)
607 fprintf (stderr, _("Try `%s --help' for more information.\n"),
608 program_name);
609 else
611 printf (_("\
612 Usage: %s [OPTION] filename.po ...\n\
613 "), program_name);
614 printf ("\n");
615 printf (_("\
616 Generate binary message catalog from textual translation description.\n\
617 "));
618 printf ("\n");
619 /* xgettext: no-wrap */
620 printf (_("\
621 Mandatory arguments to long options are mandatory for short options too.\n\
622 Similarly for optional arguments.\n\
623 "));
624 printf ("\n");
625 printf (_("\
626 Input file location:\n"));
627 printf (_("\
628 filename.po ... input files\n"));
629 printf (_("\
630 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
631 printf (_("\
632 If input file is -, standard input is read.\n"));
633 printf ("\n");
634 printf (_("\
635 Operation mode:\n"));
636 printf (_("\
637 -j, --java Java mode: generate a Java ResourceBundle class\n"));
638 printf (_("\
639 --java2 like --java, and assume Java2 (JDK 1.2 or higher)\n"));
640 printf (_("\
641 --csharp C# mode: generate a .NET .dll file\n"));
642 printf (_("\
643 --csharp-resources C# resources mode: generate a .NET .resources file\n"));
644 printf (_("\
645 --tcl Tcl mode: generate a tcl/msgcat .msg file\n"));
646 printf (_("\
647 --qt Qt mode: generate a Qt .qm file\n"));
648 printf ("\n");
649 printf (_("\
650 Output file location:\n"));
651 printf (_("\
652 -o, --output-file=FILE write output to specified file\n"));
653 printf (_("\
654 --strict enable strict Uniforum mode\n"));
655 printf (_("\
656 If output file is -, output is written to standard output.\n"));
657 printf ("\n");
658 printf (_("\
659 Output file location in Java mode:\n"));
660 printf (_("\
661 -r, --resource=RESOURCE resource name\n"));
662 printf (_("\
663 -l, --locale=LOCALE locale name, either language or language_COUNTRY\n"));
664 printf (_("\
665 -d DIRECTORY base directory of classes directory hierarchy\n"));
666 printf (_("\
667 The class name is determined by appending the locale name to the resource name,\n\
668 separated with an underscore. The -d option is mandatory. The class is\n\
669 written under the specified directory.\n\
670 "));
671 printf ("\n");
672 printf (_("\
673 Output file location in C# mode:\n"));
674 printf (_("\
675 -r, --resource=RESOURCE resource name\n"));
676 printf (_("\
677 -l, --locale=LOCALE locale name, either language or language_COUNTRY\n"));
678 printf (_("\
679 -d DIRECTORY base directory for locale dependent .dll files\n"));
680 printf (_("\
681 The -l and -d options are mandatory. The .dll file is written in a\n\
682 subdirectory of the specified directory whose name depends on the locale.\n"));
683 printf ("\n");
684 printf (_("\
685 Output file location in Tcl mode:\n"));
686 printf (_("\
687 -l, --locale=LOCALE locale name, either language or language_COUNTRY\n"));
688 printf (_("\
689 -d DIRECTORY base directory of .msg message catalogs\n"));
690 printf (_("\
691 The -l and -d options are mandatory. The .msg file is written in the\n\
692 specified directory.\n"));
693 printf ("\n");
694 printf (_("\
695 Input file syntax:\n"));
696 printf (_("\
697 -P, --properties-input input files are in Java .properties syntax\n"));
698 printf (_("\
699 --stringtable-input input files are in NeXTstep/GNUstep .strings\n\
700 syntax\n"));
701 printf ("\n");
702 printf (_("\
703 Input file interpretation:\n"));
704 printf (_("\
705 -c, --check perform all the checks implied by\n\
706 --check-format, --check-header, --check-domain\n"));
707 printf (_("\
708 --check-format check language dependent format strings\n"));
709 printf (_("\
710 --check-header verify presence and contents of the header entry\n"));
711 printf (_("\
712 --check-domain check for conflicts between domain directives\n\
713 and the --output-file option\n"));
714 printf (_("\
715 -C, --check-compatibility check that GNU msgfmt behaves like X/Open msgfmt\n"));
716 printf (_("\
717 --check-accelerators[=CHAR] check presence of keyboard accelerators for\n\
718 menu items\n"));
719 printf (_("\
720 -f, --use-fuzzy use fuzzy entries in output\n"));
721 printf ("\n");
722 printf (_("\
723 Output details:\n"));
724 printf (_("\
725 -a, --alignment=NUMBER align strings to NUMBER bytes (default: %d)\n"), DEFAULT_OUTPUT_ALIGNMENT);
726 printf (_("\
727 --no-hash binary file will not include the hash table\n"));
728 printf ("\n");
729 printf (_("\
730 Informative output:\n"));
731 printf (_("\
732 -h, --help display this help and exit\n"));
733 printf (_("\
734 -V, --version output version information and exit\n"));
735 printf (_("\
736 --statistics print statistics about translations\n"));
737 printf (_("\
738 -v, --verbose increase verbosity level\n"));
739 printf ("\n");
740 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout);
743 exit (status);
747 static const char *
748 add_mo_suffix (const char *fname)
750 size_t len;
751 char *result;
753 len = strlen (fname);
754 if (len > 3 && memcmp (fname + len - 3, ".mo", 3) == 0)
755 return fname;
756 if (len > 4 && memcmp (fname + len - 4, ".gmo", 4) == 0)
757 return fname;
758 result = (char *) xmalloc (len + 4);
759 stpcpy (stpcpy (result, fname), ".mo");
760 return result;
764 static struct msg_domain *
765 new_domain (const char *name, const char *file_name)
767 struct msg_domain **p_dom = &domain_list;
769 while (*p_dom != NULL && strcmp (name, (*p_dom)->domain_name) != 0)
770 p_dom = &(*p_dom)->next;
772 if (*p_dom == NULL)
774 struct msg_domain *domain;
776 domain = (struct msg_domain *) xmalloc (sizeof (struct msg_domain));
777 domain->mlp = message_list_alloc (true);
778 domain->domain_name = name;
779 domain->file_name = file_name;
780 domain->next = NULL;
781 *p_dom = domain;
784 return *p_dom;
788 static bool
789 is_nonobsolete (const message_ty *mp)
791 return !mp->obsolete;
795 static sigjmp_buf sigfpe_exit;
797 #if USE_SIGINFO
799 static int sigfpe_code;
801 /* Signal handler called in case of arithmetic exception (e.g. division
802 by zero) during plural_eval. */
803 static void
804 sigfpe_handler (int sig, siginfo_t *sip, void *scp)
806 sigfpe_code = sip->si_code;
807 siglongjmp (sigfpe_exit, 1);
810 #else
812 /* Signal handler called in case of arithmetic exception (e.g. division
813 by zero) during plural_eval. */
814 static void
815 sigfpe_handler (int sig)
817 siglongjmp (sigfpe_exit, 1);
820 #endif
822 static void
823 install_sigfpe_handler ()
825 #if USE_SIGINFO
826 struct sigaction action;
827 action.sa_sigaction = sigfpe_handler;
828 action.sa_flags = SA_SIGINFO;
829 sigemptyset (&action.sa_mask);
830 sigaction (SIGFPE, &action, (struct sigaction *) NULL);
831 #else
832 signal (SIGFPE, sigfpe_handler);
833 #endif
836 static void
837 uninstall_sigfpe_handler ()
839 #if USE_SIGINFO
840 struct sigaction action;
841 action.sa_handler = SIG_DFL;
842 action.sa_flags = 0;
843 sigemptyset (&action.sa_mask);
844 sigaction (SIGFPE, &action, (struct sigaction *) NULL);
845 #else
846 signal (SIGFPE, SIG_DFL);
847 #endif
850 /* Check the values returned by plural_eval. */
851 static void
852 check_plural_eval (struct expression *plural_expr,
853 unsigned long nplurals_value,
854 const lex_pos_ty *header_pos)
856 if (sigsetjmp (sigfpe_exit, 1) == 0)
858 unsigned long n;
860 /* Protect against arithmetic exceptions. */
861 install_sigfpe_handler ();
863 for (n = 0; n <= 1000; n++)
865 unsigned long val = plural_eval (plural_expr, n);
867 if ((long) val < 0)
869 /* End of protection against arithmetic exceptions. */
870 uninstall_sigfpe_handler ();
872 error_with_progname = false;
873 error_at_line (0, 0,
874 header_pos->file_name, header_pos->line_number,
875 _("plural expression can produce negative values"));
876 error_with_progname = true;
877 exit_status = EXIT_FAILURE;
878 return;
880 else if (val >= nplurals_value)
882 /* End of protection against arithmetic exceptions. */
883 uninstall_sigfpe_handler ();
885 error_with_progname = false;
886 error_at_line (0, 0,
887 header_pos->file_name, header_pos->line_number,
888 _("nplurals = %lu but plural expression can produce values as large as %lu"),
889 nplurals_value, val);
890 error_with_progname = true;
891 exit_status = EXIT_FAILURE;
892 return;
896 /* End of protection against arithmetic exceptions. */
897 uninstall_sigfpe_handler ();
899 else
901 /* Caught an arithmetic exception. */
902 const char *msg;
904 /* End of protection against arithmetic exceptions. */
905 uninstall_sigfpe_handler ();
907 #if USE_SIGINFO
908 switch (sigfpe_code)
909 #endif
911 #if USE_SIGINFO
912 # ifdef FPE_INTDIV
913 case FPE_INTDIV:
914 /* xgettext: c-format */
915 msg = _("plural expression can produce division by zero");
916 break;
917 # endif
918 # ifdef FPE_INTOVF
919 case FPE_INTOVF:
920 /* xgettext: c-format */
921 msg = _("plural expression can produce integer overflow");
922 break;
923 # endif
924 default:
925 #endif
926 /* xgettext: c-format */
927 msg = _("plural expression can produce arithmetic exceptions, possibly division by zero");
930 error_with_progname = false;
931 error_at_line (0, 0, header_pos->file_name, header_pos->line_number, msg);
932 error_with_progname = true;
933 exit_status = EXIT_FAILURE;
938 /* Perform plural expression checking. */
939 static void
940 check_plural (message_list_ty *mlp)
942 const lex_pos_ty *has_plural;
943 unsigned long min_nplurals;
944 const lex_pos_ty *min_pos;
945 unsigned long max_nplurals;
946 const lex_pos_ty *max_pos;
947 size_t j;
948 message_ty *header;
950 /* Determine whether mlp has plural entries. */
951 has_plural = NULL;
952 min_nplurals = ULONG_MAX;
953 min_pos = NULL;
954 max_nplurals = 0;
955 max_pos = NULL;
956 for (j = 0; j < mlp->nitems; j++)
958 message_ty *mp = mlp->item[j];
960 if (mp->msgid_plural != NULL)
962 const char *p;
963 const char *p_end;
964 unsigned long n;
966 if (has_plural == NULL)
967 has_plural = &mp->pos;
969 n = 0;
970 for (p = mp->msgstr, p_end = p + mp->msgstr_len;
971 p < p_end;
972 p += strlen (p) + 1)
973 n++;
974 if (min_nplurals > n)
976 min_nplurals = n;
977 min_pos = &mp->pos;
979 if (max_nplurals > n)
981 max_nplurals = n;
982 min_pos = &mp->pos;
987 /* Look at the plural entry for this domain.
988 Cf, function extract_plural_expression. */
989 header = message_list_search (mlp, "");
990 if (header != NULL)
992 const char *nullentry;
993 const char *plural;
994 const char *nplurals;
995 bool try_to_help = false;
997 nullentry = header->msgstr;
999 plural = strstr (nullentry, "plural=");
1000 nplurals = strstr (nullentry, "nplurals=");
1001 if (plural == NULL && has_plural != NULL)
1003 error_with_progname = false;
1004 error_at_line (0, 0, has_plural->file_name, has_plural->line_number,
1005 _("message catalog has plural form translations..."));
1006 --error_message_count;
1007 error_at_line (0, 0, header->pos.file_name, header->pos.line_number,
1008 _("...but header entry lacks a \"plural=EXPRESSION\" attribute"));
1009 error_with_progname = true;
1010 try_to_help = true;
1011 exit_status = EXIT_FAILURE;
1013 if (nplurals == NULL && has_plural != NULL)
1015 error_with_progname = false;
1016 error_at_line (0, 0, has_plural->file_name, has_plural->line_number,
1017 _("message catalog has plural form translations..."));
1018 --error_message_count;
1019 error_at_line (0, 0, header->pos.file_name, header->pos.line_number,
1020 _("...but header entry lacks a \"nplurals=INTEGER\" attribute"));
1021 error_with_progname = true;
1022 try_to_help = true;
1023 exit_status = EXIT_FAILURE;
1025 if (plural != NULL && nplurals != NULL)
1027 const char *endp;
1028 unsigned long int nplurals_value;
1029 struct parse_args args;
1030 struct expression *plural_expr;
1032 /* First check the number. */
1033 nplurals += 9;
1034 while (*nplurals != '\0' && isspace ((unsigned char) *nplurals))
1035 ++nplurals;
1036 endp = nplurals;
1037 nplurals_value = 0;
1038 if (*nplurals >= '0' && *nplurals <= '9')
1039 nplurals_value = strtoul (nplurals, (char **) &endp, 10);
1040 if (nplurals == endp)
1042 error_with_progname = false;
1043 error_at_line (0, 0,
1044 header->pos.file_name, header->pos.line_number,
1045 _("invalid nplurals value"));
1046 error_with_progname = true;
1047 try_to_help = true;
1048 exit_status = EXIT_FAILURE;
1051 /* Then check the expression. */
1052 plural += 7;
1053 args.cp = plural;
1054 if (parse_plural_expression (&args) != 0)
1056 error_with_progname = false;
1057 error_at_line (0, 0,
1058 header->pos.file_name, header->pos.line_number,
1059 _("invalid plural expression"));
1060 error_with_progname = true;
1061 try_to_help = true;
1062 exit_status = EXIT_FAILURE;
1064 plural_expr = args.res;
1066 /* See whether nplurals and plural fit together. */
1067 if (exit_status != EXIT_FAILURE)
1068 check_plural_eval (plural_expr, nplurals_value, &header->pos);
1070 /* Check the number of plurals of the translations. */
1071 if (exit_status != EXIT_FAILURE)
1073 if (min_nplurals < nplurals_value)
1075 error_with_progname = false;
1076 error_at_line (0, 0,
1077 header->pos.file_name, header->pos.line_number,
1078 _("nplurals = %lu..."), nplurals_value);
1079 --error_message_count;
1080 error_at_line (0, 0, min_pos->file_name, min_pos->line_number,
1081 ngettext ("...but some messages have only one plural form",
1082 "...but some messages have only %lu plural forms",
1083 min_nplurals),
1084 min_nplurals);
1085 error_with_progname = true;
1086 exit_status = EXIT_FAILURE;
1088 else if (max_nplurals > nplurals_value)
1090 error_with_progname = false;
1091 error_at_line (0, 0,
1092 header->pos.file_name, header->pos.line_number,
1093 _("nplurals = %lu..."), nplurals_value);
1094 --error_message_count;
1095 error_at_line (0, 0, max_pos->file_name, max_pos->line_number,
1096 ngettext ("...but some messages have one plural form",
1097 "...but some messages have %lu plural forms",
1098 max_nplurals),
1099 max_nplurals);
1100 error_with_progname = true;
1101 exit_status = EXIT_FAILURE;
1103 /* The only valid case is max_nplurals <= n <= min_nplurals,
1104 which means either has_plural == NULL or
1105 max_nplurals = n = min_nplurals. */
1108 /* Try to help the translator by looking up the right plural formula
1109 for her. */
1110 if (try_to_help)
1112 const char *language;
1114 language = strstr (nullentry, "Language-Team: ");
1115 if (language != NULL)
1117 language += 15;
1118 for (j = 0; j < plural_table_size; j++)
1119 if (strncmp (language,
1120 plural_table[j].language,
1121 strlen (plural_table[j].language)) == 0)
1123 char *recommended =
1124 xasprintf ("Plural-Forms: %s\\n", plural_table[j].value);
1125 fprintf (stderr,
1126 _("Try using the following, valid for %s:\n"),
1127 plural_table[j].language);
1128 fprintf (stderr, "\"%s\"\n", recommended);
1129 free (recommended);
1130 break;
1135 else if (has_plural != NULL)
1137 error_with_progname = false;
1138 error_at_line (0, 0, has_plural->file_name, has_plural->line_number,
1139 _("message catalog has plural form translations, but lacks a header entry with \"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\""));
1140 error_with_progname = true;
1141 exit_status = EXIT_FAILURE;
1146 /* Signal an error when checking format strings. */
1147 static lex_pos_ty curr_msgid_pos;
1148 static void
1149 formatstring_error_logger (const char *format, ...)
1151 va_list args;
1153 va_start (args, format);
1154 fprintf (stderr, "%s:%lu: ",
1155 curr_msgid_pos.file_name,
1156 (unsigned long) curr_msgid_pos.line_number);
1157 vfprintf (stderr, format, args);
1158 putc ('\n', stderr);
1159 fflush (stderr);
1160 va_end (args);
1161 ++error_message_count;
1165 /* Perform miscellaneous checks on a message. */
1166 static void
1167 check_pair (const char *msgid,
1168 const lex_pos_ty *msgid_pos,
1169 const char *msgid_plural,
1170 const char *msgstr, size_t msgstr_len,
1171 const lex_pos_ty *msgstr_pos, enum is_format is_format[NFORMATS])
1173 int has_newline;
1174 unsigned int j;
1175 const char *p;
1177 /* If the msgid string is empty we have the special entry reserved for
1178 information about the translation. */
1179 if (msgid[0] == '\0')
1180 return;
1182 /* Test 1: check whether all or none of the strings begin with a '\n'. */
1183 has_newline = (msgid[0] == '\n');
1184 #define TEST_NEWLINE(p) (p[0] == '\n')
1185 if (msgid_plural != NULL)
1187 if (TEST_NEWLINE(msgid_plural) != has_newline)
1189 error_with_progname = false;
1190 error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
1191 _("\
1192 `msgid' and `msgid_plural' entries do not both begin with '\\n'"));
1193 error_with_progname = true;
1194 exit_status = EXIT_FAILURE;
1196 for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++)
1197 if (TEST_NEWLINE(p) != has_newline)
1199 error_with_progname = false;
1200 error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
1201 _("\
1202 `msgid' and `msgstr[%u]' entries do not both begin with '\\n'"), j);
1203 error_with_progname = true;
1204 exit_status = EXIT_FAILURE;
1207 else
1209 if (TEST_NEWLINE(msgstr) != has_newline)
1211 error_with_progname = false;
1212 error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
1213 _("\
1214 `msgid' and `msgstr' entries do not both begin with '\\n'"));
1215 error_with_progname = true;
1216 exit_status = EXIT_FAILURE;
1219 #undef TEST_NEWLINE
1221 /* Test 2: check whether all or none of the strings end with a '\n'. */
1222 has_newline = (msgid[strlen (msgid) - 1] == '\n');
1223 #define TEST_NEWLINE(p) (p[0] != '\0' && p[strlen (p) - 1] == '\n')
1224 if (msgid_plural != NULL)
1226 if (TEST_NEWLINE(msgid_plural) != has_newline)
1228 error_with_progname = false;
1229 error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
1230 _("\
1231 `msgid' and `msgid_plural' entries do not both end with '\\n'"));
1232 error_with_progname = true;
1233 exit_status = EXIT_FAILURE;
1235 for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++)
1236 if (TEST_NEWLINE(p) != has_newline)
1238 error_with_progname = false;
1239 error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
1240 _("\
1241 `msgid' and `msgstr[%u]' entries do not both end with '\\n'"), j);
1242 error_with_progname = true;
1243 exit_status = EXIT_FAILURE;
1246 else
1248 if (TEST_NEWLINE(msgstr) != has_newline)
1250 error_with_progname = false;
1251 error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
1252 _("\
1253 `msgid' and `msgstr' entries do not both end with '\\n'"));
1254 error_with_progname = true;
1255 exit_status = EXIT_FAILURE;
1258 #undef TEST_NEWLINE
1260 if (check_compatibility && msgid_plural != NULL)
1262 error_with_progname = false;
1263 error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
1264 _("plural handling is a GNU gettext extension"));
1265 error_with_progname = true;
1266 exit_status = EXIT_FAILURE;
1269 if (check_format_strings)
1270 /* Test 3: Check whether both formats strings contain the same number
1271 of format specifications. */
1273 curr_msgid_pos = *msgid_pos;
1274 if (check_msgid_msgstr_format (msgid, msgid_plural, msgstr, msgstr_len,
1275 is_format, formatstring_error_logger))
1276 exit_status = EXIT_FAILURE;
1279 if (check_accelerators && msgid_plural == NULL)
1280 /* Test 4: Check that if msgid is a menu item with a keyboard accelerator,
1281 the msgstr has an accelerator as well. A keyboard accelerator is
1282 designated by an immediately preceding '&'. We cannot check whether
1283 two accelerators collide, only whether the translator has bothered
1284 thinking about them. */
1286 const char *p;
1288 /* We are only interested in msgids that contain exactly one '&'. */
1289 p = strchr (msgid, accelerator_char);
1290 if (p != NULL && strchr (p + 1, accelerator_char) == NULL)
1292 /* Count the number of '&' in msgstr, but ignore '&&'. */
1293 unsigned int count = 0;
1295 for (p = msgstr; (p = strchr (p, accelerator_char)) != NULL; p++)
1296 if (p[1] == accelerator_char)
1297 p++;
1298 else
1299 count++;
1301 if (count == 0)
1303 error_with_progname = false;
1304 error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
1305 _("msgstr lacks the keyboard accelerator mark '%c'"),
1306 accelerator_char);
1307 error_with_progname = true;
1309 else if (count > 1)
1311 error_with_progname = false;
1312 error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
1313 _("msgstr has too many keyboard accelerator marks '%c'"),
1314 accelerator_char);
1315 error_with_progname = true;
1322 /* Perform miscellaneous checks on a header entry. */
1323 static void
1324 check_header_entry (const char *msgstr_string)
1326 static const char *required_fields[] =
1328 "Project-Id-Version", "PO-Revision-Date", "Last-Translator",
1329 "Language-Team", "MIME-Version", "Content-Type",
1330 "Content-Transfer-Encoding"
1332 static const char *default_values[] =
1334 "PACKAGE VERSION", "YEAR-MO-DA", "FULL NAME", "LANGUAGE", NULL,
1335 "text/plain; charset=CHARSET", "ENCODING"
1337 const size_t nfields = SIZEOF (required_fields);
1338 int initial = -1;
1339 int cnt;
1341 for (cnt = 0; cnt < nfields; ++cnt)
1343 char *endp = strstr (msgstr_string, required_fields[cnt]);
1345 if (endp == NULL)
1346 multiline_error (xasprintf ("%s: ", gram_pos.file_name),
1347 xasprintf (_("headerfield `%s' missing in header\n"),
1348 required_fields[cnt]));
1349 else if (endp != msgstr_string && endp[-1] != '\n')
1350 multiline_error (xasprintf ("%s: ", gram_pos.file_name),
1351 xasprintf (_("\
1352 header field `%s' should start at beginning of line\n"),
1353 required_fields[cnt]));
1354 else if (default_values[cnt] != NULL
1355 && strncmp (default_values[cnt],
1356 endp + strlen (required_fields[cnt]) + 2,
1357 strlen (default_values[cnt])) == 0)
1359 if (initial != -1)
1361 multiline_error (xasprintf ("%s: ", gram_pos.file_name),
1362 xstrdup (_("\
1363 some header fields still have the initial default value\n")));
1364 initial = -1;
1365 break;
1367 else
1368 initial = cnt;
1372 if (initial != -1)
1373 multiline_error (xasprintf ("%s: ", gram_pos.file_name),
1374 xasprintf (_("\
1375 field `%s' still has initial default value\n"),
1376 required_fields[initial]));
1380 /* The rest of the file defines a subclass msgfmt_po_reader_ty of
1381 default_po_reader_ty. Its particularities are:
1382 - The header entry check is performed on-the-fly.
1383 - Comments are not stored, they are discarded right away.
1384 (This is achieved by setting handle_comments = false and
1385 handle_filepos_comments = false.)
1386 - The multi-domain handling is adapted to our domain_list.
1390 /* This structure defines a derived class of the default_po_reader_ty class.
1391 (See read-po-abstract.h for an explanation.) */
1392 typedef struct msgfmt_po_reader_ty msgfmt_po_reader_ty;
1393 struct msgfmt_po_reader_ty
1395 /* inherited instance variables, etc */
1396 DEFAULT_PO_READER_TY
1398 bool has_header_entry;
1399 bool has_nonfuzzy_header_entry;
1403 /* Prepare for first message. */
1404 static void
1405 msgfmt_constructor (abstract_po_reader_ty *that)
1407 msgfmt_po_reader_ty *this = (msgfmt_po_reader_ty *) that;
1409 /* Invoke superclass constructor. */
1410 default_constructor (that);
1412 this->has_header_entry = false;
1413 this->has_nonfuzzy_header_entry = false;
1417 /* Some checks after whole file is read. */
1418 static void
1419 msgfmt_parse_debrief (abstract_po_reader_ty *that)
1421 msgfmt_po_reader_ty *this = (msgfmt_po_reader_ty *) that;
1423 /* Invoke superclass method. */
1424 default_parse_debrief (that);
1426 /* Test whether header entry was found. */
1427 if (check_header)
1429 if (!this->has_header_entry)
1431 multiline_error (xasprintf ("%s: ", gram_pos.file_name),
1432 xasprintf (_("\
1433 warning: PO file header missing or invalid\n")));
1434 multiline_error (NULL,
1435 xasprintf (_("\
1436 warning: charset conversion will not work\n")));
1438 else if (!this->has_nonfuzzy_header_entry)
1440 /* Has only a fuzzy header entry. Since the versions 0.10.xx
1441 ignore a fuzzy header entry and even give an error on it, we
1442 give a warning, to increase operability with these older
1443 msgfmt versions. This warning can go away in January 2003. */
1444 multiline_warning (xasprintf ("%s: ", gram_pos.file_name),
1445 xasprintf (_("warning: PO file header fuzzy\n")));
1446 multiline_warning (NULL,
1447 xasprintf (_("\
1448 warning: older versions of msgfmt will give an error on this\n")));
1454 /* Set 'domain' directive when seen in .po file. */
1455 static void
1456 msgfmt_set_domain (default_po_reader_ty *this, char *name)
1458 /* If no output file was given, we change it with each `domain'
1459 directive. */
1460 if (!java_mode && !csharp_mode && !csharp_resources_mode && !tcl_mode
1461 && !qt_mode && output_file_name == NULL)
1463 size_t correct;
1465 correct = strcspn (name, INVALID_PATH_CHAR);
1466 if (name[correct] != '\0')
1468 exit_status = EXIT_FAILURE;
1469 if (correct == 0)
1471 error (0, 0, _("\
1472 domain name \"%s\" not suitable as file name"), name);
1473 return;
1475 else
1476 error (0, 0, _("\
1477 domain name \"%s\" not suitable as file name: will use prefix"), name);
1478 name[correct] = '\0';
1481 /* Set new domain. */
1482 current_domain = new_domain (name, add_mo_suffix (name));
1483 this->domain = current_domain->domain_name;
1484 this->mlp = current_domain->mlp;
1486 else
1488 if (check_domain)
1489 po_gram_error_at_line (&gram_pos,
1490 _("`domain %s' directive ignored"), name);
1492 /* NAME was allocated in po-gram-gen.y but is not used anywhere. */
1493 free (name);
1498 static void
1499 msgfmt_add_message (default_po_reader_ty *this,
1500 char *msgid,
1501 lex_pos_ty *msgid_pos,
1502 char *msgid_plural,
1503 char *msgstr, size_t msgstr_len,
1504 lex_pos_ty *msgstr_pos,
1505 bool force_fuzzy, bool obsolete)
1507 /* Check whether already a domain is specified. If not, use default
1508 domain. */
1509 if (current_domain == NULL)
1511 current_domain = new_domain (MESSAGE_DOMAIN_DEFAULT,
1512 add_mo_suffix (MESSAGE_DOMAIN_DEFAULT));
1513 /* Keep current_domain and this->domain synchronized. */
1514 this->domain = current_domain->domain_name;
1515 this->mlp = current_domain->mlp;
1518 /* Invoke superclass method. */
1519 default_add_message (this, msgid, msgid_pos, msgid_plural,
1520 msgstr, msgstr_len, msgstr_pos, force_fuzzy, obsolete);
1524 static void
1525 msgfmt_frob_new_message (default_po_reader_ty *that, message_ty *mp,
1526 const lex_pos_ty *msgid_pos,
1527 const lex_pos_ty *msgstr_pos)
1529 msgfmt_po_reader_ty *this = (msgfmt_po_reader_ty *) that;
1531 if (!mp->obsolete)
1533 /* Don't emit untranslated entries.
1534 Also don't emit fuzzy entries, unless --use-fuzzy was specified.
1535 But ignore fuzziness of the header entry. */
1536 if (mp->msgstr[0] == '\0'
1537 || (!include_all && mp->is_fuzzy && mp->msgid[0] != '\0'))
1539 if (check_compatibility)
1541 error_with_progname = false;
1542 error_at_line (0, 0, mp->pos.file_name, mp->pos.line_number,
1543 (mp->msgstr[0] == '\0'
1544 ? _("empty `msgstr' entry ignored")
1545 : _("fuzzy `msgstr' entry ignored")));
1546 error_with_progname = true;
1549 /* Increment counter for fuzzy/untranslated messages. */
1550 if (mp->msgstr[0] == '\0')
1551 ++msgs_untranslated;
1552 else
1553 ++msgs_fuzzy;
1555 mp->obsolete = true;
1557 else
1559 /* Test for header entry. */
1560 if (mp->msgid[0] == '\0')
1562 this->has_header_entry = true;
1563 if (!mp->is_fuzzy)
1564 this->has_nonfuzzy_header_entry = true;
1566 /* Do some more tests on the contents of the header entry. */
1567 if (check_header)
1568 check_header_entry (mp->msgstr);
1570 else
1571 /* We don't count the header entry in the statistic so place
1572 the counter incrementation here. */
1573 if (mp->is_fuzzy)
1574 ++msgs_fuzzy;
1575 else
1576 ++msgs_translated;
1578 /* Do some more checks on both strings. */
1579 check_pair (mp->msgid, msgid_pos, mp->msgid_plural,
1580 mp->msgstr, mp->msgstr_len, msgstr_pos,
1581 mp->is_format);
1587 /* Test for `#, fuzzy' comments and warn. */
1588 static void
1589 msgfmt_comment_special (abstract_po_reader_ty *that, const char *s)
1591 msgfmt_po_reader_ty *this = (msgfmt_po_reader_ty *) that;
1593 /* Invoke superclass method. */
1594 default_comment_special (that, s);
1596 if (this->is_fuzzy)
1598 static bool warned = false;
1600 if (!include_all && check_compatibility && !warned)
1602 warned = true;
1603 error (0, 0, _("\
1604 %s: warning: source file contains fuzzy translation"),
1605 gram_pos.file_name);
1611 /* So that the one parser can be used for multiple programs, and also
1612 use good data hiding and encapsulation practices, an object
1613 oriented approach has been taken. An object instance is allocated,
1614 and all actions resulting from the parse will be through
1615 invocations of method functions of that object. */
1617 static default_po_reader_class_ty msgfmt_methods =
1620 sizeof (msgfmt_po_reader_ty),
1621 msgfmt_constructor,
1622 default_destructor,
1623 default_parse_brief,
1624 msgfmt_parse_debrief,
1625 default_directive_domain,
1626 default_directive_message,
1627 default_comment,
1628 default_comment_dot,
1629 default_comment_filepos,
1630 msgfmt_comment_special
1632 msgfmt_set_domain, /* set_domain */
1633 msgfmt_add_message, /* add_message */
1634 msgfmt_frob_new_message /* frob_new_message */
1638 /* Read .po file FILENAME and store translation pairs. */
1639 static void
1640 read_po_file_msgfmt (char *filename)
1642 char *real_filename;
1643 FILE *fp = open_po_file (filename, &real_filename, true);
1644 default_po_reader_ty *pop;
1646 pop = default_po_reader_alloc (&msgfmt_methods);
1647 pop->handle_comments = false;
1648 pop->handle_filepos_comments = false;
1649 pop->allow_domain_directives = true;
1650 pop->allow_duplicates = false;
1651 pop->allow_duplicates_if_same_msgstr = false;
1652 pop->mdlp = NULL;
1653 pop->mlp = NULL;
1654 if (current_domain != NULL)
1656 /* Keep current_domain and this->domain synchronized. */
1657 pop->domain = current_domain->domain_name;
1658 pop->mlp = current_domain->mlp;
1660 po_lex_pass_obsolete_entries (true);
1661 po_scan ((abstract_po_reader_ty *) pop, fp, real_filename, filename,
1662 input_syntax);
1663 po_reader_free ((abstract_po_reader_ty *) pop);
1665 if (fp != stdin)
1666 fclose (fp);