#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <ctype.h>
#include <getopt.h>
#include <limits.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <locale.h>
#include "closeout.h"
#include "dir-list.h"
#include "error.h"
#include "error-progname.h"
#include "progname.h"
#include "relocatable.h"
#include "basename.h"
#include "xerror.h"
#include "format.h"
#include "xalloc.h"
#include "plural-exp.h"
#include "plural-table.h"
#include "strstr.h"
#include "stpcpy.h"
#include "exit.h"
#include "msgfmt.h"
#include "write-mo.h"
#include "write-java.h"
#include "write-tcl.h"
#include "write-qt.h"
#include "gettext.h"
#include "message.h"
#include "open-po.h"
#include "read-po.h"
#include "po-charset.h"
#define _(str) gettext (str)
#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
#if defined _MSC_VER || defined __MINGW32__
# define sigjmp_buf jmp_buf
# define sigsetjmp(env,savesigs) setjmp (env)
# define siglongjmp longjmp
#endif
#if HAVE_SIGINFO && !defined (__sgi)
# define USE_SIGINFO 1
#endif
static int exit_status;
static bool include_all = false;
static const char *output_file_name;
static bool java_mode;
static bool assume_java2;
static const char *java_resource_name;
static const char *java_locale_name;
static const char *java_class_directory;
static bool tcl_mode;
static const char *tcl_locale_name;
static const char *tcl_base_directory;
static bool qt_mode;
struct msg_domain
{
message_list_ty *mlp;
const char *domain_name;
const char *file_name;
struct msg_domain *next;
};
static struct msg_domain *domain_list;
static struct msg_domain *current_domain;
bool verbose = false;
static bool check_format_strings = false;
static bool check_header = false;
static bool check_domain = false;
static bool check_compatibility = false;
static bool check_accelerators = false;
static char accelerator_char = '&';
static int msgs_translated;
static int msgs_untranslated;
static int msgs_fuzzy;
static int do_statistics;
static const struct option long_options[] =
{
{ "alignment", required_argument, NULL, 'a' },
{ "check", no_argument, NULL, 'c' },
{ "check-accelerators", optional_argument, NULL, CHAR_MAX + 1 },
{ "check-compatibility", no_argument, NULL, 'C' },
{ "check-domain", no_argument, NULL, CHAR_MAX + 2 },
{ "check-format", no_argument, NULL, CHAR_MAX + 3 },
{ "check-header", no_argument, NULL, CHAR_MAX + 4 },
{ "directory", required_argument, NULL, 'D' },
{ "help", no_argument, NULL, 'h' },
{ "java", no_argument, NULL, 'j' },
{ "java2", no_argument, NULL, CHAR_MAX + 5 },
{ "locale", required_argument, NULL, 'l' },
{ "no-hash", no_argument, NULL, CHAR_MAX + 6 },
{ "output-file", required_argument, NULL, 'o' },
{ "properties-input", no_argument, NULL, 'P' },
{ "qt", no_argument, NULL, CHAR_MAX + 9 },
{ "resource", required_argument, NULL, 'r' },
{ "statistics", no_argument, &do_statistics, 1 },
{ "strict", no_argument, NULL, 'S' },
{ "stringtable-input", no_argument, NULL, CHAR_MAX + 8 },
{ "tcl", no_argument, NULL, CHAR_MAX + 7 },
{ "use-fuzzy", no_argument, NULL, 'f' },
{ "verbose", no_argument, NULL, 'v' },
{ "version", no_argument, NULL, 'V' },
{ NULL, 0, NULL, 0 }
};
static void usage (int status)
#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
__attribute__ ((noreturn))
#endif
;
static const char *add_mo_suffix (const char *);
static struct msg_domain *new_domain (const char *name, const char *file_name);
static bool is_nonobsolete (const message_ty *mp);
static void check_plural (message_list_ty *mlp);
static void read_po_file_msgfmt (char *filename);
int
main (int argc, char *argv[])
{
int opt;
bool do_help = false;
bool do_version = false;
bool strict_uniforum = false;
const char *canon_encoding;
struct msg_domain *domain;
alignment = DEFAULT_OUTPUT_ALIGNMENT;
set_program_name (argv[0]);
error_print_progname = maybe_print_progname;
error_one_per_line = 1;
exit_status = EXIT_SUCCESS;
#ifdef HAVE_SETLOCALE
setlocale (LC_ALL, "");
#endif
bindtextdomain (PACKAGE, relocate (LOCALEDIR));
textdomain (PACKAGE);
atexit (close_stdout);
while ((opt = getopt_long (argc, argv, "a:cCd:D:fhjl:o:Pr:vV", long_options,
NULL))
!= EOF)
switch (opt)
{
case '\0':
break;
case 'a':
{
char *endp;
size_t new_align = strtoul (optarg, &endp, 0);
if (endp != optarg)
alignment = new_align;
}
break;
case 'c':
check_domain = true;
check_format_strings = true;
check_header = true;
break;
case 'C':
check_compatibility = true;
break;
case 'd':
java_class_directory = optarg;
tcl_base_directory = optarg;
break;
case 'D':
dir_list_append (optarg);
break;
case 'f':
include_all = true;
break;
case 'h':
do_help = true;
break;
case 'j':
java_mode = true;
break;
case 'l':
java_locale_name = optarg;
tcl_locale_name = optarg;
break;
case 'o':
output_file_name = optarg;
break;
case 'P':
input_syntax = syntax_properties;
break;
case 'r':
java_resource_name = optarg;
break;
case 'S':
strict_uniforum = true;
break;
case 'v':
verbose = true;
break;
case 'V':
do_version = true;
break;
case CHAR_MAX + 1:
check_accelerators = true;
if (optarg != NULL)
{
if (optarg[0] != '\0' && ispunct ((unsigned char) optarg[0])
&& optarg[1] == '\0')
accelerator_char = optarg[0];
else
error (EXIT_FAILURE, 0,
_("the argument to %s should be a single punctuation character"),
"--check-accelerators");
}
break;
case CHAR_MAX + 2:
check_domain = true;
break;
case CHAR_MAX + 3:
check_format_strings = true;
break;
case CHAR_MAX + 4:
check_header = true;
break;
case CHAR_MAX + 5:
java_mode = true;
assume_java2 = true;
break;
case CHAR_MAX + 6:
no_hash_table = true;
break;
case CHAR_MAX + 7:
tcl_mode = true;
break;
case CHAR_MAX + 8:
input_syntax = syntax_stringtable;
break;
case CHAR_MAX + 9:
qt_mode = true;
break;
default:
usage (EXIT_FAILURE);
break;
}
if (do_version)
{
printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
This is free software; see the source for copying conditions. There is NO\n\
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
"),
"1995-1998, 2000-2003");
printf (_("Written by %s.\n"), "Ulrich Drepper");
exit (EXIT_SUCCESS);
}
if (do_help)
usage (EXIT_SUCCESS);
if (optind >= argc)
{
error (EXIT_SUCCESS, 0, _("no input file given"));
usage (EXIT_FAILURE);
}
if (java_mode && tcl_mode)
error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
"--java", "--tcl");
if (java_mode && qt_mode)
error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
"--java", "--qt");
if (tcl_mode && qt_mode)
error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
"--tcl", "--qt");
if (java_mode)
{
if (output_file_name != NULL)
{
error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
"--java", "--output-file");
}
if (java_class_directory == NULL)
{
error (EXIT_SUCCESS, 0,
_("%s requires a \"-d directory\" specification"),
"--java");
usage (EXIT_FAILURE);
}
}
else if (tcl_mode)
{
if (output_file_name != NULL)
{
error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
"--tcl", "--output-file");
}
if (tcl_locale_name == NULL)
{
error (EXIT_SUCCESS, 0,
_("%s requires a \"-l locale\" specification"),
"--tcl");
usage (EXIT_FAILURE);
}
if (tcl_base_directory == NULL)
{
error (EXIT_SUCCESS, 0,
_("%s requires a \"-d directory\" specification"),
"--tcl");
usage (EXIT_FAILURE);
}
}
else
{
if (java_resource_name != NULL)
{
error (EXIT_SUCCESS, 0, _("%s is only valid with %s"),
"--resource", "--java");
usage (EXIT_FAILURE);
}
if (java_locale_name != NULL)
{
error (EXIT_SUCCESS, 0, _("%s is only valid with %s or %s"),
"--locale", "--java", "--tcl");
usage (EXIT_FAILURE);
}
if (java_class_directory != NULL)
{
error (EXIT_SUCCESS, 0, _("%s is only valid with %s or %s"),
"-d", "--java", "--tcl");
usage (EXIT_FAILURE);
}
}
if (output_file_name != NULL)
current_domain =
new_domain (output_file_name,
!qt_mode && strict_uniforum ? add_mo_suffix (output_file_name)
: output_file_name);
while (argc > optind)
{
if (output_file_name == NULL)
current_domain = NULL;
read_po_file_msgfmt (argv[optind]);
++optind;
}
canon_encoding =
(input_syntax == syntax_properties || input_syntax == syntax_stringtable
? po_charset_utf8
: NULL);
for (domain = domain_list; domain != NULL; domain = domain->next)
message_list_remove_if_not (domain->mlp, is_nonobsolete);
if (check_header)
for (domain = domain_list; domain != NULL; domain = domain->next)
check_plural (domain->mlp);
for (domain = domain_list; domain != NULL; domain = domain->next)
{
if (java_mode)
{
if (msgdomain_write_java (domain->mlp, canon_encoding,
java_resource_name, java_locale_name,
java_class_directory, assume_java2))
exit_status = EXIT_FAILURE;
}
else if (tcl_mode)
{
if (msgdomain_write_tcl (domain->mlp, canon_encoding,
tcl_locale_name, tcl_base_directory))
exit_status = EXIT_FAILURE;
}
else if (qt_mode)
{
if (msgdomain_write_qt (domain->mlp, canon_encoding,
domain->domain_name, domain->file_name))
exit_status = EXIT_FAILURE;
}
else
{
if (msgdomain_write_mo (domain->mlp, domain->domain_name,
domain->file_name))
exit_status = EXIT_FAILURE;
}
message_list_free (domain->mlp);
}
if (verbose || do_statistics)
{
fprintf (stderr,
ngettext ("%d translated message", "%d translated messages",
msgs_translated),
msgs_translated);
if (msgs_fuzzy > 0)
fprintf (stderr,
ngettext (", %d fuzzy translation", ", %d fuzzy translations",
msgs_fuzzy),
msgs_fuzzy);
if (msgs_untranslated > 0)
fprintf (stderr,
ngettext (", %d untranslated message",
", %d untranslated messages",
msgs_untranslated),
msgs_untranslated);
fputs (".\n", stderr);
}
exit (exit_status);
}
static void
usage (int status)
{
if (status != EXIT_SUCCESS)
fprintf (stderr, _("Try `%s --help' for more information.\n"),
program_name);
else
{
printf (_("\
Usage: %s [OPTION] filename.po ...\n\
"), program_name);
printf ("\n");
printf (_("\
Generate binary message catalog from textual translation description.\n\
"));
printf ("\n");
printf (_("\
Mandatory arguments to long options are mandatory for short options too.\n\
Similarly for optional arguments.\n\
"));
printf ("\n");
printf (_("\
Input file location:\n"));
printf (_("\
filename.po ... input files\n"));
printf (_("\
-D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
printf (_("\
If input file is -, standard input is read.\n"));
printf ("\n");
printf (_("\
Operation mode:\n"));
printf (_("\
-j, --java Java mode: generate a Java ResourceBundle class\n"));
printf (_("\
--java2 like --java, and assume Java2 (JDK 1.2 or higher)\n"));
printf (_("\
--tcl Tcl mode: generate a tcl/msgcat .msg file\n"));
printf (_("\
--qt Qt mode: generate a Qt .qm file\n"));
printf ("\n");
printf (_("\
Output file location:\n"));
printf (_("\
-o, --output-file=FILE write output to specified file\n"));
printf (_("\
--strict enable strict Uniforum mode\n"));
printf (_("\
If output file is -, output is written to standard output.\n"));
printf ("\n");
printf (_("\
Output file location in Java mode:\n"));
printf (_("\
-r, --resource=RESOURCE resource name\n"));
printf (_("\
-l, --locale=LOCALE locale name, either language or language_COUNTRY\n"));
printf (_("\
-d DIRECTORY base directory of classes directory hierarchy\n"));
printf (_("\
The class name is determined by appending the locale name to the resource name,\n\
separated with an underscore. The -d option is mandatory. The class is\n\
written under the specified directory.\n\
"));
printf ("\n");
printf (_("\
Output file location in Tcl mode:\n"));
printf (_("\
-l, --locale=LOCALE locale name, either language or language_COUNTRY\n"));
printf (_("\
-d DIRECTORY base directory of .msg message catalogs\n"));
printf (_("\
The -l and -d options are mandatory. The .msg file is written in the\n\
specified directory.\n"));
printf ("\n");
printf (_("\
Input file syntax:\n"));
printf (_("\
-P, --properties-input input files are in Java .properties syntax\n"));
printf (_("\
--stringtable-input input files are in NeXTstep/GNUstep .strings\n\
syntax\n"));
printf ("\n");
printf (_("\
Input file interpretation:\n"));
printf (_("\
-c, --check perform all the checks implied by\n\
--check-format, --check-header, --check-domain\n"));
printf (_("\
--check-format check language dependent format strings\n"));
printf (_("\
--check-header verify presence and contents of the header entry\n"));
printf (_("\
--check-domain check for conflicts between domain directives\n\
and the --output-file option\n"));
printf (_("\
-C, --check-compatibility check that GNU msgfmt behaves like X/Open msgfmt\n"));
printf (_("\
--check-accelerators[=CHAR] check presence of keyboard accelerators for\n\
menu items\n"));
printf (_("\
-f, --use-fuzzy use fuzzy entries in output\n"));
printf ("\n");
printf (_("\
Output details:\n"));
printf (_("\
-a, --alignment=NUMBER align strings to NUMBER bytes (default: %d)\n"), DEFAULT_OUTPUT_ALIGNMENT);
printf (_("\
--no-hash binary file will not include the hash table\n"));
printf ("\n");
printf (_("\
Informative output:\n"));
printf (_("\
-h, --help display this help and exit\n"));
printf (_("\
-V, --version output version information and exit\n"));
printf (_("\
--statistics print statistics about translations\n"));
printf (_("\
-v, --verbose increase verbosity level\n"));
printf ("\n");
fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout);
}
exit (status);
}
static const char *
add_mo_suffix (const char *fname)
{
size_t len;
char *result;
len = strlen (fname);
if (len > 3 && memcmp (fname + len - 3, ".mo", 3) == 0)
return fname;
if (len > 4 && memcmp (fname + len - 4, ".gmo", 4) == 0)
return fname;
result = (char *) xmalloc (len + 4);
stpcpy (stpcpy (result, fname), ".mo");
return result;
}
static struct msg_domain *
new_domain (const char *name, const char *file_name)
{
struct msg_domain **p_dom = &domain_list;
while (*p_dom != NULL && strcmp (name, (*p_dom)->domain_name) != 0)
p_dom = &(*p_dom)->next;
if (*p_dom == NULL)
{
struct msg_domain *domain;
domain = (struct msg_domain *) xmalloc (sizeof (struct msg_domain));
domain->mlp = message_list_alloc (true);
domain->domain_name = name;
domain->file_name = file_name;
domain->next = NULL;
*p_dom = domain;
}
return *p_dom;
}
static bool
is_nonobsolete (const message_ty *mp)
{
return !mp->obsolete;
}
static sigjmp_buf sigfpe_exit;
#if USE_SIGINFO
static int sigfpe_code;
static void
sigfpe_handler (int sig, siginfo_t *sip, void *scp)
{
sigfpe_code = sip->si_code;
siglongjmp (sigfpe_exit, 1);
}
#else
static void
sigfpe_handler (int sig)
{
siglongjmp (sigfpe_exit, 1);
}
#endif
static void
install_sigfpe_handler ()
{
#if USE_SIGINFO
struct sigaction action;
action.sa_sigaction = sigfpe_handler;
action.sa_flags = SA_SIGINFO;
sigemptyset (&action.sa_mask);
sigaction (SIGFPE, &action, (struct sigaction *) NULL);
#else
signal (SIGFPE, sigfpe_handler);
#endif
}
static void
uninstall_sigfpe_handler ()
{
#if USE_SIGINFO
struct sigaction action;
action.sa_handler = SIG_DFL;
action.sa_flags = 0;
sigemptyset (&action.sa_mask);
sigaction (SIGFPE, &action, (struct sigaction *) NULL);
#else
signal (SIGFPE, SIG_DFL);
#endif
}
static void
check_plural_eval (struct expression *plural_expr,
unsigned long nplurals_value,
const lex_pos_ty *header_pos)
{
if (sigsetjmp (sigfpe_exit, 1) == 0)
{
unsigned long n;
install_sigfpe_handler ();
for (n = 0; n <= 1000; n++)
{
unsigned long val = plural_eval (plural_expr, n);
if ((long) val < 0)
{
uninstall_sigfpe_handler ();
error_with_progname = false;
error_at_line (0, 0,
header_pos->file_name, header_pos->line_number,
_("plural expression can produce negative values"));
error_with_progname = true;
exit_status = EXIT_FAILURE;
return;
}
else if (val >= nplurals_value)
{
uninstall_sigfpe_handler ();
error_with_progname = false;
error_at_line (0, 0,
header_pos->file_name, header_pos->line_number,
_("nplurals = %lu but plural expression can produce values as large as %lu"),
nplurals_value, val);
error_with_progname = true;
exit_status = EXIT_FAILURE;
return;
}
}
uninstall_sigfpe_handler ();
}
else
{
const char *msg;
uninstall_sigfpe_handler ();
#if USE_SIGINFO
switch (sigfpe_code)
#endif
{
#if USE_SIGINFO
# ifdef FPE_INTDIV
case FPE_INTDIV:
msg = _("plural expression can produce division by zero");
break;
# endif
# ifdef FPE_INTOVF
case FPE_INTOVF:
msg = _("plural expression can produce integer overflow");
break;
# endif
default:
#endif
msg = _("plural expression can produce arithmetic exceptions, possibly division by zero");
}
error_with_progname = false;
error_at_line (0, 0, header_pos->file_name, header_pos->line_number, msg);
error_with_progname = true;
exit_status = EXIT_FAILURE;
}
}
static void
check_plural (message_list_ty *mlp)
{
const lex_pos_ty *has_plural;
unsigned long min_nplurals;
const lex_pos_ty *min_pos;
unsigned long max_nplurals;
const lex_pos_ty *max_pos;
size_t j;
message_ty *header;
has_plural = NULL;
min_nplurals = ULONG_MAX;
min_pos = NULL;
max_nplurals = 0;
max_pos = NULL;
for (j = 0; j < mlp->nitems; j++)
{
message_ty *mp = mlp->item[j];
if (mp->msgid_plural != NULL)
{
const char *p;
const char *p_end;
unsigned long n;
if (has_plural == NULL)
has_plural = &mp->pos;
n = 0;
for (p = mp->msgstr, p_end = p + mp->msgstr_len;
p < p_end;
p += strlen (p) + 1)
n++;
if (min_nplurals > n)
{
min_nplurals = n;
min_pos = &mp->pos;
}
if (max_nplurals > n)
{
max_nplurals = n;
min_pos = &mp->pos;
}
}
}
header = message_list_search (mlp, "");
if (header != NULL)
{
const char *nullentry;
const char *plural;
const char *nplurals;
bool try_to_help = false;
nullentry = header->msgstr;
plural = strstr (nullentry, "plural=");
nplurals = strstr (nullentry, "nplurals=");
if (plural == NULL && has_plural != NULL)
{
error_with_progname = false;
error_at_line (0, 0, has_plural->file_name, has_plural->line_number,
_("message catalog has plural form translations..."));
--error_message_count;
error_at_line (0, 0, header->pos.file_name, header->pos.line_number,
_("...but header entry lacks a \"plural=EXPRESSION\" attribute"));
error_with_progname = true;
try_to_help = true;
exit_status = EXIT_FAILURE;
}
if (nplurals == NULL && has_plural != NULL)
{
error_with_progname = false;
error_at_line (0, 0, has_plural->file_name, has_plural->line_number,
_("message catalog has plural form translations..."));
--error_message_count;
error_at_line (0, 0, header->pos.file_name, header->pos.line_number,
_("...but header entry lacks a \"nplurals=INTEGER\" attribute"));
error_with_progname = true;
try_to_help = true;
exit_status = EXIT_FAILURE;
}
if (plural != NULL && nplurals != NULL)
{
const char *endp;
unsigned long int nplurals_value;
struct parse_args args;
struct expression *plural_expr;
nplurals += 9;
while (*nplurals != '\0' && isspace ((unsigned char) *nplurals))
++nplurals;
endp = nplurals;
nplurals_value = 0;
if (*nplurals >= '0' && *nplurals <= '9')
nplurals_value = strtoul (nplurals, (char **) &endp, 10);
if (nplurals == endp)
{
error_with_progname = false;
error_at_line (0, 0,
header->pos.file_name, header->pos.line_number,
_("invalid nplurals value"));
error_with_progname = true;
try_to_help = true;
exit_status = EXIT_FAILURE;
}
plural += 7;
args.cp = plural;
if (parse_plural_expression (&args) != 0)
{
error_with_progname = false;
error_at_line (0, 0,
header->pos.file_name, header->pos.line_number,
_("invalid plural expression"));
error_with_progname = true;
try_to_help = true;
exit_status = EXIT_FAILURE;
}
plural_expr = args.res;
if (exit_status != EXIT_FAILURE)
check_plural_eval (plural_expr, nplurals_value, &header->pos);
if (exit_status != EXIT_FAILURE)
{
if (min_nplurals < nplurals_value)
{
error_with_progname = false;
error_at_line (0, 0,
header->pos.file_name, header->pos.line_number,
_("nplurals = %lu..."), nplurals_value);
--error_message_count;
error_at_line (0, 0, min_pos->file_name, min_pos->line_number,
ngettext ("...but some messages have only one plural form",
"...but some messages have only %lu plural forms",
min_nplurals),
min_nplurals);
error_with_progname = true;
exit_status = EXIT_FAILURE;
}
else if (max_nplurals > nplurals_value)
{
error_with_progname = false;
error_at_line (0, 0,
header->pos.file_name, header->pos.line_number,
_("nplurals = %lu..."), nplurals_value);
--error_message_count;
error_at_line (0, 0, max_pos->file_name, max_pos->line_number,
ngettext ("...but some messages have one plural form",
"...but some messages have %lu plural forms",
max_nplurals),
max_nplurals);
error_with_progname = true;
exit_status = EXIT_FAILURE;
}
}
}
if (try_to_help)
{
const char *language;
language = strstr (nullentry, "Language-Team: ");
if (language != NULL)
{
language += 15;
for (j = 0; j < plural_table_size; j++)
if (strncmp (language,
plural_table[j].language,
strlen (plural_table[j].language)) == 0)
{
char *recommended =
xasprintf ("Plural-Forms: %s\\n", plural_table[j].value);
fprintf (stderr,
_("Try using the following, valid for %s:\n"),
plural_table[j].language);
fprintf (stderr, "\"%s\"\n", recommended);
free (recommended);
break;
}
}
}
}
else if (has_plural != NULL)
{
error_with_progname = false;
error_at_line (0, 0, has_plural->file_name, has_plural->line_number,
_("message catalog has plural form translations, but lacks a header entry with \"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\""));
error_with_progname = true;
exit_status = EXIT_FAILURE;
}
}
static void
check_pair (const char *msgid,
const lex_pos_ty *msgid_pos,
const char *msgid_plural,
const char *msgstr, size_t msgstr_len,
const lex_pos_ty *msgstr_pos, enum is_format is_format[NFORMATS])
{
int has_newline;
size_t i;
unsigned int j;
const char *p;
if (msgid[0] == '\0')
return;
has_newline = (msgid[0] == '\n');
#define TEST_NEWLINE(p) (p[0] == '\n')
if (msgid_plural != NULL)
{
if (TEST_NEWLINE(msgid_plural) != has_newline)
{
error_with_progname = false;
error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
_("\
`msgid' and `msgid_plural' entries do not both begin with '\\n'"));
error_with_progname = true;
exit_status = EXIT_FAILURE;
}
for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++)
if (TEST_NEWLINE(p) != has_newline)
{
error_with_progname = false;
error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
_("\
`msgid' and `msgstr[%u]' entries do not both begin with '\\n'"), j);
error_with_progname = true;
exit_status = EXIT_FAILURE;
}
}
else
{
if (TEST_NEWLINE(msgstr) != has_newline)
{
error_with_progname = false;
error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
_("\
`msgid' and `msgstr' entries do not both begin with '\\n'"));
error_with_progname = true;
exit_status = EXIT_FAILURE;
}
}
#undef TEST_NEWLINE
has_newline = (msgid[strlen (msgid) - 1] == '\n');
#define TEST_NEWLINE(p) (p[0] != '\0' && p[strlen (p) - 1] == '\n')
if (msgid_plural != NULL)
{
if (TEST_NEWLINE(msgid_plural) != has_newline)
{
error_with_progname = false;
error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
_("\
`msgid' and `msgid_plural' entries do not both end with '\\n'"));
error_with_progname = true;
exit_status = EXIT_FAILURE;
}
for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++)
if (TEST_NEWLINE(p) != has_newline)
{
error_with_progname = false;
error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
_("\
`msgid' and `msgstr[%u]' entries do not both end with '\\n'"), j);
error_with_progname = true;
exit_status = EXIT_FAILURE;
}
}
else
{
if (TEST_NEWLINE(msgstr) != has_newline)
{
error_with_progname = false;
error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
_("\
`msgid' and `msgstr' entries do not both end with '\\n'"));
error_with_progname = true;
exit_status = EXIT_FAILURE;
}
}
#undef TEST_NEWLINE
if (check_compatibility && msgid_plural != NULL)
{
error_with_progname = false;
error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
_("plural handling is a GNU gettext extension"));
error_with_progname = true;
exit_status = EXIT_FAILURE;
}
if (check_format_strings)
for (i = 0; i < NFORMATS; i++)
if (possible_format_p (is_format[i]))
{
struct formatstring_parser *parser = formatstring_parsers[i];
char *invalid_reason = NULL;
void *msgid_descr =
parser->parse (msgid_plural != NULL ? msgid_plural : msgid,
&invalid_reason);
if (msgid_descr != NULL)
{
char buf[18+1];
const char *pretty_msgstr = "msgstr";
const char *p_end = msgstr + msgstr_len;
const char *p;
for (p = msgstr, j = 0; p < p_end; p += strlen (p) + 1, j++)
{
void *msgstr_descr;
if (msgid_plural != NULL)
{
sprintf (buf, "msgstr[%u]", j);
pretty_msgstr = buf;
}
msgstr_descr = parser->parse (p, &invalid_reason);
if (msgstr_descr != NULL)
{
if (parser->check (msgid_pos, msgid_descr, msgstr_descr,
msgid_plural == NULL,
true, pretty_msgstr))
exit_status = EXIT_FAILURE;
parser->free (msgstr_descr);
}
else
{
error_with_progname = false;
error_at_line (0, 0, msgid_pos->file_name,
msgid_pos->line_number,
_("\
'%s' is not a valid %s format string, unlike 'msgid'. Reason: %s"),
pretty_msgstr, format_language_pretty[i],
invalid_reason);
error_with_progname = true;
exit_status = EXIT_FAILURE;
free (invalid_reason);
}
}
parser->free (msgid_descr);
}
else
free (invalid_reason);
}
if (check_accelerators && msgid_plural == NULL)
{
const char *p;
p = strchr (msgid, accelerator_char);
if (p != NULL && strchr (p + 1, accelerator_char) == NULL)
{
unsigned int count = 0;
for (p = msgstr; (p = strchr (p, accelerator_char)) != NULL; p++)
if (p[1] == accelerator_char)
p++;
else
count++;
if (count == 0)
{
error_with_progname = false;
error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
_("msgstr lacks the keyboard accelerator mark '%c'"),
accelerator_char);
error_with_progname = true;
}
else if (count > 1)
{
error_with_progname = false;
error_at_line (0, 0, msgid_pos->file_name, msgid_pos->line_number,
_("msgstr has too many keyboard accelerator marks '%c'"),
accelerator_char);
error_with_progname = true;
}
}
}
}
static void
check_header_entry (const char *msgstr_string)
{
static const char *required_fields[] =
{
"Project-Id-Version", "PO-Revision-Date", "Last-Translator",
"Language-Team", "MIME-Version", "Content-Type",
"Content-Transfer-Encoding"
};
static const char *default_values[] =
{
"PACKAGE VERSION", "YEAR-MO-DA", "FULL NAME", "LANGUAGE", NULL,
"text/plain; charset=CHARSET", "ENCODING"
};
const size_t nfields = SIZEOF (required_fields);
int initial = -1;
int cnt;
for (cnt = 0; cnt < nfields; ++cnt)
{
char *endp = strstr (msgstr_string, required_fields[cnt]);
if (endp == NULL)
multiline_error (xasprintf ("%s: ", gram_pos.file_name),
xasprintf (_("headerfield `%s' missing in header\n"),
required_fields[cnt]));
else if (endp != msgstr_string && endp[-1] != '\n')
multiline_error (xasprintf ("%s: ", gram_pos.file_name),
xasprintf (_("\
header field `%s' should start at beginning of line\n"),
required_fields[cnt]));
else if (default_values[cnt] != NULL
&& strncmp (default_values[cnt],
endp + strlen (required_fields[cnt]) + 2,
strlen (default_values[cnt])) == 0)
{
if (initial != -1)
{
multiline_error (xasprintf ("%s: ", gram_pos.file_name),
xstrdup (_("\
some header fields still have the initial default value\n")));
initial = -1;
break;
}
else
initial = cnt;
}
}
if (initial != -1)
multiline_error (xasprintf ("%s: ", gram_pos.file_name),
xasprintf (_("\
field `%s' still has initial default value\n"),
required_fields[initial]));
}
typedef struct msgfmt_po_reader_ty msgfmt_po_reader_ty;
struct msgfmt_po_reader_ty
{
DEFAULT_PO_READER_TY
bool has_header_entry;
bool has_nonfuzzy_header_entry;
};
static void
msgfmt_constructor (abstract_po_reader_ty *that)
{
msgfmt_po_reader_ty *this = (msgfmt_po_reader_ty *) that;
default_constructor (that);
this->has_header_entry = false;
this->has_nonfuzzy_header_entry = false;
}
static void
msgfmt_parse_debrief (abstract_po_reader_ty *that)
{
msgfmt_po_reader_ty *this = (msgfmt_po_reader_ty *) that;
default_parse_debrief (that);
if (check_header)
{
if (!this->has_header_entry)
{
multiline_error (xasprintf ("%s: ", gram_pos.file_name),
xasprintf (_("\
warning: PO file header missing or invalid\n")));
multiline_error (NULL,
xasprintf (_("\
warning: charset conversion will not work\n")));
}
else if (!this->has_nonfuzzy_header_entry)
{
multiline_warning (xasprintf ("%s: ", gram_pos.file_name),
xasprintf (_("warning: PO file header fuzzy\n")));
multiline_warning (NULL,
xasprintf (_("\
warning: older versions of msgfmt will give an error on this\n")));
}
}
}
static void
msgfmt_set_domain (default_po_reader_ty *this, char *name)
{
if (!java_mode && !tcl_mode && !qt_mode && output_file_name == NULL)
{
size_t correct;
correct = strcspn (name, INVALID_PATH_CHAR);
if (name[correct] != '\0')
{
exit_status = EXIT_FAILURE;
if (correct == 0)
{
error (0, 0, _("\
domain name \"%s\" not suitable as file name"), name);
return;
}
else
error (0, 0, _("\
domain name \"%s\" not suitable as file name: will use prefix"), name);
name[correct] = '\0';
}
current_domain = new_domain (name, add_mo_suffix (name));
this->domain = current_domain->domain_name;
this->mlp = current_domain->mlp;
}
else
{
if (check_domain)
po_gram_error_at_line (&gram_pos,
_("`domain %s' directive ignored"), name);
free (name);
}
}
static void
msgfmt_add_message (default_po_reader_ty *this,
char *msgid,
lex_pos_ty *msgid_pos,
char *msgid_plural,
char *msgstr, size_t msgstr_len,
lex_pos_ty *msgstr_pos,
bool force_fuzzy, bool obsolete)
{
if (current_domain == NULL)
{
current_domain = new_domain (MESSAGE_DOMAIN_DEFAULT,
add_mo_suffix (MESSAGE_DOMAIN_DEFAULT));
this->domain = current_domain->domain_name;
this->mlp = current_domain->mlp;
}
default_add_message (this, msgid, msgid_pos, msgid_plural,
msgstr, msgstr_len, msgstr_pos, force_fuzzy, obsolete);
}
static void
msgfmt_frob_new_message (default_po_reader_ty *that, message_ty *mp,
const lex_pos_ty *msgid_pos,
const lex_pos_ty *msgstr_pos)
{
msgfmt_po_reader_ty *this = (msgfmt_po_reader_ty *) that;
if (!mp->obsolete)
{
if (mp->msgstr[0] == '\0'
|| (!include_all && mp->is_fuzzy && mp->msgid[0] != '\0'))
{
if (check_compatibility)
{
error_with_progname = false;
error_at_line (0, 0, mp->pos.file_name, mp->pos.line_number,
(mp->msgstr[0] == '\0'
? _("empty `msgstr' entry ignored")
: _("fuzzy `msgstr' entry ignored")));
error_with_progname = true;
}
if (mp->msgstr[0] == '\0')
++msgs_untranslated;
else
++msgs_fuzzy;
mp->obsolete = true;
}
else
{
if (mp->msgid[0] == '\0')
{
this->has_header_entry = true;
if (!mp->is_fuzzy)
this->has_nonfuzzy_header_entry = true;
if (check_header)
check_header_entry (mp->msgstr);
}
else
if (mp->is_fuzzy)
++msgs_fuzzy;
else
++msgs_translated;
check_pair (mp->msgid, msgid_pos, mp->msgid_plural,
mp->msgstr, mp->msgstr_len, msgstr_pos,
mp->is_format);
}
}
}
static void
msgfmt_comment_special (abstract_po_reader_ty *that, const char *s)
{
msgfmt_po_reader_ty *this = (msgfmt_po_reader_ty *) that;
default_comment_special (that, s);
if (this->is_fuzzy)
{
static bool warned = false;
if (!include_all && check_compatibility && !warned)
{
warned = true;
error (0, 0, _("\
%s: warning: source file contains fuzzy translation"),
gram_pos.file_name);
}
}
}
static default_po_reader_class_ty msgfmt_methods =
{
{
sizeof (msgfmt_po_reader_ty),
msgfmt_constructor,
default_destructor,
default_parse_brief,
msgfmt_parse_debrief,
default_directive_domain,
default_directive_message,
default_comment,
default_comment_dot,
default_comment_filepos,
msgfmt_comment_special
},
msgfmt_set_domain,
msgfmt_add_message,
msgfmt_frob_new_message
};
static void
read_po_file_msgfmt (char *filename)
{
char *real_filename;
FILE *fp = open_po_file (filename, &real_filename, true);
default_po_reader_ty *pop;
pop = default_po_reader_alloc (&msgfmt_methods);
pop->handle_comments = false;
pop->handle_filepos_comments = false;
pop->allow_domain_directives = true;
pop->allow_duplicates = false;
pop->allow_duplicates_if_same_msgstr = false;
pop->mdlp = NULL;
pop->mlp = NULL;
if (current_domain != NULL)
{
pop->domain = current_domain->domain_name;
pop->mlp = current_domain->mlp;
}
po_lex_pass_obsolete_entries (true);
po_scan ((abstract_po_reader_ty *) pop, fp, real_filename, filename,
input_syntax);
po_reader_free ((abstract_po_reader_ty *) pop);
if (fp != stdin)
fclose (fp);
}