summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--gettext-tools/src/ChangeLog27
-rw-r--r--gettext-tools/src/FILES12
-rw-r--r--gettext-tools/src/Makefile.am9
-rw-r--r--gettext-tools/src/format.c4
-rw-r--r--gettext-tools/src/format.h4
-rw-r--r--gettext-tools/src/gettext-po.c170
-rw-r--r--gettext-tools/src/gettext-po.h24
-rw-r--r--gettext-tools/src/msgfmt.c687
-rw-r--r--gettext-tools/src/msgl-check.c740
-rw-r--r--gettext-tools/src/msgl-check.h51
10 files changed, 992 insertions, 736 deletions
diff --git a/gettext-tools/src/ChangeLog b/gettext-tools/src/ChangeLog
index 76a410d..3a0f193 100644
--- a/gettext-tools/src/ChangeLog
+++ b/gettext-tools/src/ChangeLog
@@ -1,5 +1,32 @@
2005-09-17 Bruno Haible <bruno@clisp.org>
+ * msgl-check.h: New file.
+ * msgl-check.c: New file, mostly extracted from msgfmt.c.
+ * Makefile.am (noinst_HEADERS): Add msgl-check.h.
+ (libgettextsrc_la_SOURCES): Add msgl-check.c, plural-eval.c.
+ (msgfmt_SOURCES): Remove plural-eval.c.
+ * format.c (check_msgid_msgstr_format): Add const to argument type.
+ * format.h (check_msgid_msgstr_format): Add const to argument type.
+ * gettext-po.c: Include msgl-check.h.
+ (po_file_check_all, po_message_check_all): New functions.
+ (po_xerror_logger): Remove function.
+ (po_message_check_format): Use new check_message function.
+ * gettext-po.h (po_file_check_all, po_message_check_all): New
+ declarations.
+ * msgfmt.c: Include msgl-check.h instead of setjmp.h, signal.h,
+ stdarg.h, po-xerror.h, format.h, plural-exp.h, plural-table.h,
+ strstr.h.
+ (SIZEOF, sigjmp_buf, sigsetjmp, siglongjmp, USE_SIGINFO,
+ sigfpe_exit, sigfpe_code, sigfpe_handler, install_sigfpe_handler,
+ uninstall_sigfpe_handler, check_plural_eval, plural_help, check_plural,
+ curr_mp, curr_msgid_pos, formatstring_error_logger, check_pair,
+ check_header_entry): Move definitions to msgl-check.c.
+ (main): Update.
+ (msgfmt_frob_new_message): Call check_message instead of
+ check_header_entry and check_pair.
+
+2005-09-17 Bruno Haible <bruno@clisp.org>
+
Use new error handlers in libgettextpo.
* gettext-po.h (PO_SEVERITY_WARNING, PO_SEVERITY_ERROR,
PO_SEVERITY_FATAL_ERROR): New macros.
diff --git a/gettext-tools/src/FILES b/gettext-tools/src/FILES
index 0ab3c8f..d812d49 100644
--- a/gettext-tools/src/FILES
+++ b/gettext-tools/src/FILES
@@ -209,13 +209,17 @@ format-gcc-internal.c Format string handling GCC internal.
format-qt.c Format string handling for Qt.
format.c Table of the language dependent format string handlers.
+plural.c
+ Parsing plural expressions.
+plural-eval.c
+ Evaluating plural expressions.
+msgl-check.h
+msgl-check.c
+ Checking of messages.
+
+-------------- The 'msgfmt' program
| msgfmt.h
| Declarations.
-| plural.c
-| Parsing plural expressions.
-| plural-eval.c
-| Evaluating plursl expressions.
| write-mo.h
| write-mo.c
| Generating GNU .mo files.
diff --git a/gettext-tools/src/Makefile.am b/gettext-tools/src/Makefile.am
index 46a882c..375469d 100644
--- a/gettext-tools/src/Makefile.am
+++ b/gettext-tools/src/Makefile.am
@@ -41,7 +41,7 @@ str-list.h \
write-po.h write-properties.h write-stringtable.h \
dir-list.h file-list.h po-gram-gen.h po-gram-gen2.h \
msgl-charset.h msgl-equal.h msgl-iconv.h msgl-ascii.h msgl-cat.h \
-msgl-english.h msgfmt.h msgunfmt.h plural-count.h \
+msgl-english.h msgl-check.h msgfmt.h msgunfmt.h plural-count.h \
read-mo.h write-mo.h \
read-java.h write-java.h \
read-csharp.h write-csharp.h \
@@ -112,8 +112,9 @@ format-php.c format-gcc-internal.c format-qt.c
# libgettextsrc contains all code that is needed by at least two programs.
libgettextsrc_la_SOURCES = \
$(COMMON_SOURCE) read-po.c write-properties.c write-stringtable.c write-po.c \
-msgl-ascii.c msgl-iconv.c msgl-equal.c msgl-cat.c msgl-english.c file-list.c \
-msgl-charset.c po-time.c plural.c plural-table.c $(FORMAT_SOURCE)
+msgl-ascii.c msgl-iconv.c msgl-equal.c msgl-cat.c msgl-english.c msgl-check.c \
+file-list.c msgl-charset.c po-time.c plural.c plural-eval.c plural-table.c \
+$(FORMAT_SOURCE)
# libgettextpo contains the public API for PO files.
libgettextpo_la_SOURCES = gettext-po.c
@@ -134,7 +135,7 @@ msgcmp_SOURCES = msgcmp.c
msgfmt_SOURCES = msgfmt.c
msgfmt_SOURCES += \
write-mo.c write-java.c write-csharp.c write-resources.c write-tcl.c \
- write-qt.c plural-eval.c ../../gettext-runtime/intl/hash-string.c
+ write-qt.c ../../gettext-runtime/intl/hash-string.c
if !MINGW
msgmerge_SOURCES = msgmerge.c
else
diff --git a/gettext-tools/src/format.c b/gettext-tools/src/format.c
index dcabc32..9c0b11e 100644
--- a/gettext-tools/src/format.c
+++ b/gettext-tools/src/format.c
@@ -1,5 +1,5 @@
/* Format strings.
- Copyright (C) 2001-2004 Free Software Foundation, Inc.
+ Copyright (C) 2001-2005 Free Software Foundation, Inc.
Written by Bruno Haible <haible@clisp.cons.org>, 2001.
This program is free software; you can redistribute it and/or modify
@@ -61,7 +61,7 @@ struct formatstring_parser *formatstring_parsers[NFORMATS] =
bool
check_msgid_msgstr_format (const char *msgid, const char *msgid_plural,
const char *msgstr, size_t msgstr_len,
- enum is_format is_format[NFORMATS],
+ const enum is_format is_format[NFORMATS],
formatstring_error_logger_t error_logger)
{
bool err = false;
diff --git a/gettext-tools/src/format.h b/gettext-tools/src/format.h
index ea5b223..80a609a 100644
--- a/gettext-tools/src/format.h
+++ b/gettext-tools/src/format.h
@@ -1,5 +1,5 @@
/* Format strings.
- Copyright (C) 2001-2004 Free Software Foundation, Inc.
+ Copyright (C) 2001-2005 Free Software Foundation, Inc.
Written by Bruno Haible <haible@clisp.cons.org>, 2001.
This program is free software; you can redistribute it and/or modify
@@ -111,7 +111,7 @@ extern void
extern bool
check_msgid_msgstr_format (const char *msgid, const char *msgid_plural,
const char *msgstr, size_t msgstr_len,
- enum is_format is_format[NFORMATS],
+ const enum is_format is_format[NFORMATS],
formatstring_error_logger_t error_logger);
diff --git a/gettext-tools/src/gettext-po.c b/gettext-tools/src/gettext-po.c
index 5efcdd5..1f9bc40 100644
--- a/gettext-tools/src/gettext-po.c
+++ b/gettext-tools/src/gettext-po.c
@@ -40,6 +40,7 @@
#include "po-xerror.h"
#include "vasprintf.h"
#include "format.h"
+#include "msgl-check.h"
#include "gettext.h"
#define _(str) gettext(str)
@@ -958,21 +959,133 @@ po_message_set_format (po_message_t message, const char *format_type, /*bool*/in
}
-/* An error logger based on the po_xerror function pointer. */
-static void
-po_xerror_logger (const char *format, ...)
+/* Return the file name. */
+
+const char *
+po_filepos_file (po_filepos_t filepos)
{
- va_list args;
- char *error_message;
+ lex_pos_ty *pp = (lex_pos_ty *) filepos;
- va_start (args, format);
- if (vasprintf (&error_message, format, args) < 0)
- error (EXIT_FAILURE, 0, _("memory exhausted"));
- va_end (args);
- po_xerror (PO_SEVERITY_ERROR, NULL, NULL, 0, 0, false, error_message);
- free (error_message);
+ return pp->file_name;
+}
+
+
+/* Return the line number where the string starts, or (size_t)(-1) if no line
+ number is available. */
+
+size_t
+po_filepos_start_line (po_filepos_t filepos)
+{
+ lex_pos_ty *pp = (lex_pos_ty *) filepos;
+
+ return pp->line_number;
+}
+
+
+/* Test whether an entire file PO file is valid, like msgfmt does it.
+ If it is invalid, pass the reasons to the handler. */
+
+void
+po_file_check_all (po_file_t file, po_xerror_handler_t handler)
+{
+ msgdomain_list_ty *mdlp;
+ size_t k;
+
+ /* Establish error handler. */
+ po_xerror =
+ (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
+ handler->xerror;
+ po_xerror2 =
+ (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *))
+ handler->xerror2;
+
+ mdlp = file->mdlp;
+ for (k = 0; k < mdlp->nitems; k++)
+ {
+ message_list_ty *mlp = mdlp->item[k]->messages;
+ size_t j;
+
+ for (j = 0; j < mlp->nitems; j++)
+ {
+ message_ty *mp = mlp->item[j];
+
+ check_message (mp, &mp->pos, &mp->pos, 1, 1, 1, 0, 0, 0);
+ }
+
+ check_plural (mlp);
+ }
+
+ /* Restore error handler. */
+ po_xerror = textmode_xerror;
+ po_xerror2 = textmode_xerror2;
+}
+
+
+/* Test a single message, to be inserted in a PO file in memory, like msgfmt
+ does it. If it is invalid, pass the reasons to the handler. The iterator
+ is not modified by this call; it only specifies the file and the domain. */
+
+void
+po_message_check_all (po_message_t message, po_message_iterator_t iterator,
+ po_xerror_handler_t handler)
+{
+ message_ty *mp = (message_ty *) message;
+
+ /* Establish error handler. */
+ po_xerror =
+ (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
+ handler->xerror;
+ po_xerror2 =
+ (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *))
+ handler->xerror2;
+
+ check_message (mp, &mp->pos, &mp->pos, 1, 1, 1, 0, 0, 0);
+
+ /* For plural checking, combine the message and its header into a small,
+ two-element message list. */
+ {
+ message_ty *header;
+
+ /* Find the header. */
+ {
+ message_list_ty *mlp;
+ size_t j;
+
+ header = NULL;
+ mlp =
+ msgdomain_list_sublist (iterator->file->mdlp, iterator->domain, false);
+ if (mlp != NULL)
+ for (j = 0; j < mlp->nitems; j++)
+ if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
+ {
+ header = mlp->item[j];
+ break;
+ }
+ }
+
+ {
+ message_ty *items[2];
+ struct message_list_ty ml;
+ ml.item = items;
+ ml.nitems = 0;
+ ml.nitems_max = 2;
+ ml.use_hashtable = false;
+
+ if (header != NULL)
+ message_list_append (&ml, header);
+ if (mp != header)
+ message_list_append (&ml, mp);
+
+ check_plural (&ml);
+ }
+ }
+
+ /* Restore error handler. */
+ po_xerror = textmode_xerror;
+ po_xerror2 = textmode_xerror2;
}
+
/* Test whether the message translation is a valid format string if the message
is marked as being a format string. If it is invalid, pass the reasons to
the handler. */
@@ -981,17 +1094,19 @@ po_message_check_format (po_message_t message, po_xerror_handler_t handler)
{
message_ty *mp = (message_ty *) message;
- /* Establish error handler for po_xerror_logger(). */
+ /* Establish error handler. */
po_xerror =
(void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
handler->xerror;
+ po_xerror2 =
+ (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *))
+ handler->xerror2;
- check_msgid_msgstr_format (mp->msgid, mp->msgid_plural,
- mp->msgstr, mp->msgstr_len,
- mp->is_format, po_xerror_logger);
+ check_message (mp, &mp->pos, &mp->pos, 0, 1, 0, 0, 0, 0);
/* Restore error handler. */
- po_xerror = textmode_xerror;
+ po_xerror = textmode_xerror;
+ po_xerror2 = textmode_xerror2;
}
#undef po_message_check_format
@@ -1030,26 +1145,3 @@ po_message_check_format (po_message_t message, po_error_handler_t handler)
/* Restore error handler. */
po_error = error;
}
-
-
-/* Return the file name. */
-
-const char *
-po_filepos_file (po_filepos_t filepos)
-{
- lex_pos_ty *pp = (lex_pos_ty *) filepos;
-
- return pp->file_name;
-}
-
-
-/* Return the line number where the string starts, or (size_t)(-1) if no line
- number is available. */
-
-size_t
-po_filepos_start_line (po_filepos_t filepos)
-{
- lex_pos_ty *pp = (lex_pos_ty *) filepos;
-
- return pp->line_number;
-}
diff --git a/gettext-tools/src/gettext-po.h b/gettext-tools/src/gettext-po.h
index c3927be..0f215c2 100644
--- a/gettext-tools/src/gettext-po.h
+++ b/gettext-tools/src/gettext-po.h
@@ -264,12 +264,6 @@ extern int po_message_is_format (po_message_t message, const char *format_type);
/* Change the format string mark for a given type of a message. */
extern void po_message_set_format (po_message_t message, const char *format_type, /*bool*/int value);
-/* Test whether the message translation is a valid format string if the message
- is marked as being a format string. If it is invalid, pass the reasons to
- the handler. */
-#define po_message_check_format po_message_check_format_v2
-extern void po_message_check_format (po_message_t message, po_xerror_handler_t handler);
-
/* =========================== po_filepos_t API ============================ */
@@ -281,6 +275,24 @@ extern const char * po_filepos_file (po_filepos_t filepos);
extern size_t po_filepos_start_line (po_filepos_t filepos);
+/* ============================= Checking API ============================== */
+
+/* Test whether an entire file PO file is valid, like msgfmt does it.
+ If it is invalid, pass the reasons to the handler. */
+extern void po_file_check_all (po_file_t file, po_xerror_handler_t handler);
+
+/* Test a single message, to be inserted in a PO file in memory, like msgfmt
+ does it. If it is invalid, pass the reasons to the handler. The iterator
+ is not modified by this call; it only specifies the file and the domain. */
+extern void po_message_check_all (po_message_t message, po_message_iterator_t iterator, po_xerror_handler_t handler);
+
+/* Test whether the message translation is a valid format string if the message
+ is marked as being a format string. If it is invalid, pass the reasons to
+ the handler. */
+#define po_message_check_format po_message_check_format_v2
+extern void po_message_check_format (po_message_t message, po_xerror_handler_t handler);
+
+
#ifdef __cplusplus
}
#endif
diff --git a/gettext-tools/src/msgfmt.c b/gettext-tools/src/msgfmt.c
index 3fd3fcd..d9d3152 100644
--- a/gettext-tools/src/msgfmt.c
+++ b/gettext-tools/src/msgfmt.c
@@ -23,12 +23,9 @@
#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 <stdarg.h>
#include <locale.h>
#include "closeout.h"
@@ -39,12 +36,7 @@
#include "relocatable.h"
#include "basename.h"
#include "xerror.h"
-#include "po-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"
@@ -60,25 +52,10 @@
#include "open-po.h"
#include "read-po.h"
#include "po-charset.h"
+#include "msgl-check.h"
#define _(str) gettext (str)
-#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
-
-/* Some platforms don't have the sigjmp_buf type in <setjmp.h>. */
-#if defined _MSC_VER || defined __MINGW32__
-/* Native Woe32 API. */
-# define sigjmp_buf jmp_buf
-# define sigsetjmp(env,savesigs) setjmp (env)
-# define siglongjmp longjmp
-#endif
-
-/* We use siginfo to get precise information about the signal.
- But siginfo doesn't work on Irix 6.5. */
-#if HAVE_SIGINFO && !defined (__sgi)
-# define USE_SIGINFO 1
-#endif
-
/* Contains exit status for case in which no premature exit occurs. */
static int exit_status;
@@ -210,7 +187,6 @@ static void usage (int status)
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);
@@ -548,7 +524,8 @@ warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
/* Check the plural expression is present if needed and has valid syntax. */
if (check_header)
for (domain = domain_list; domain != NULL; domain = domain->next)
- check_plural (domain->mlp);
+ if (check_plural (domain->mlp))
+ exit_status = EXIT_FAILURE;
/* Now write out all domains. */
for (domain = domain_list; domain != NULL; domain = domain->next)
@@ -815,650 +792,6 @@ is_nonobsolete (const message_ty *mp)
}
-static sigjmp_buf sigfpe_exit;
-
-#if USE_SIGINFO
-
-static int sigfpe_code;
-
-/* Signal handler called in case of arithmetic exception (e.g. division
- by zero) during plural_eval. */
-static void
-sigfpe_handler (int sig, siginfo_t *sip, void *scp)
-{
- sigfpe_code = sip->si_code;
- siglongjmp (sigfpe_exit, 1);
-}
-
-#else
-
-/* Signal handler called in case of arithmetic exception (e.g. division
- by zero) during plural_eval. */
-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
-}
-
-/* Check the values returned by plural_eval. */
-static void
-check_plural_eval (struct expression *plural_expr,
- unsigned long nplurals_value,
- const message_ty *header)
-{
- if (sigsetjmp (sigfpe_exit, 1) == 0)
- {
- unsigned long n;
-
- /* Protect against arithmetic exceptions. */
- install_sigfpe_handler ();
-
- for (n = 0; n <= 1000; n++)
- {
- unsigned long val = plural_eval (plural_expr, n);
-
- if ((long) val < 0)
- {
- /* End of protection against arithmetic exceptions. */
- uninstall_sigfpe_handler ();
-
- po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false,
- _("plural expression can produce negative values"));
- exit_status = EXIT_FAILURE;
- return;
- }
- else if (val >= nplurals_value)
- {
- char *msg;
-
- /* End of protection against arithmetic exceptions. */
- uninstall_sigfpe_handler ();
-
- msg = xasprintf (_("nplurals = %lu but plural expression can produce values as large as %lu"),
- nplurals_value, val);
- po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
- free (msg);
- exit_status = EXIT_FAILURE;
- return;
- }
- }
-
- /* End of protection against arithmetic exceptions. */
- uninstall_sigfpe_handler ();
- }
- else
- {
- /* Caught an arithmetic exception. */
- const char *msg;
-
- /* End of protection against arithmetic exceptions. */
- 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");
- }
-
- po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
- exit_status = EXIT_FAILURE;
- }
-}
-
-
-/* Try to help the translator by looking up the right plural formula for her.
- Return a freshly allocated multiline help string, or NULL. */
-static char *
-plural_help (const char *nullentry)
-{
- const char *language;
- size_t j;
-
- 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 *helpline1 =
- xasprintf (_("Try using the following, valid for %s:"),
- plural_table[j].language);
- char *help =
- xasprintf ("%s\n\"Plural-Forms: %s\\n\"\n",
- helpline1, plural_table[j].value);
- free (helpline1);
- return help;
- }
- }
- return NULL;
-}
-
-
-/* Perform plural expression checking. */
-static void
-check_plural (message_list_ty *mlp)
-{
- const message_ty *has_plural;
- unsigned long min_nplurals;
- const message_ty *min_pos;
- unsigned long max_nplurals;
- const message_ty *max_pos;
- size_t j;
- message_ty *header;
-
- /* Determine whether mlp has plural entries. */
- 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;
-
- 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;
- }
- if (max_nplurals < n)
- {
- max_nplurals = n;
- max_pos = mp;
- }
- }
- }
-
- /* Look at the plural entry for this domain.
- Cf, function extract_plural_expression. */
- header = message_list_search (mlp, "");
- if (header != NULL)
- {
- const char *nullentry;
- const char *plural;
- const char *nplurals;
-
- nullentry = header->msgstr;
-
- plural = strstr (nullentry, "plural=");
- nplurals = strstr (nullentry, "nplurals=");
- if (plural == NULL && has_plural != NULL)
- {
- const char *msg1 =
- _("message catalog has plural form translations");
- const char *msg2 =
- _("but header entry lacks a \"plural=EXPRESSION\" attribute");
- char *help = plural_help (nullentry);
-
- if (help != NULL)
- {
- char *msg2ext = xasprintf ("%s\n%s", msg2, help);
- po_xerror2 (PO_SEVERITY_ERROR,
- has_plural, NULL, 0, 0, false, msg1,
- header, NULL, 0, 0, true, msg2ext);
- free (msg2ext);
- free (help);
- }
- else
- po_xerror2 (PO_SEVERITY_ERROR,
- has_plural, NULL, 0, 0, false, msg1,
- header, NULL, 0, 0, false, msg2);
-
- exit_status = EXIT_FAILURE;
- }
- if (nplurals == NULL && has_plural != NULL)
- {
- const char *msg1 =
- _("message catalog has plural form translations");
- const char *msg2 =
- _("but header entry lacks a \"nplurals=INTEGER\" attribute");
- char *help = plural_help (nullentry);
-
- if (help != NULL)
- {
- char *msg2ext = xasprintf ("%s\n%s", msg2, help);
- po_xerror2 (PO_SEVERITY_ERROR,
- has_plural, NULL, 0, 0, false, msg1,
- header, NULL, 0, 0, true, msg2ext);
- free (msg2ext);
- free (help);
- }
- else
- po_xerror2 (PO_SEVERITY_ERROR,
- has_plural, NULL, 0, 0, false, msg1,
- header, NULL, 0, 0, false, msg2);
-
- 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;
-
- /* First check the number. */
- 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)
- {
- const char *msg = _("invalid nplurals value");
- char *help = plural_help (nullentry);
-
- if (help != NULL)
- {
- char *msgext = xasprintf ("%s\n%s", msg, help);
- po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, true,
- msgext);
- free (msgext);
- free (help);
- }
- else
- po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
-
- exit_status = EXIT_FAILURE;
- }
-
- /* Then check the expression. */
- plural += 7;
- args.cp = plural;
- if (parse_plural_expression (&args) != 0)
- {
- const char *msg = _("invalid plural expression");
- char *help = plural_help (nullentry);
-
- if (help != NULL)
- {
- char *msgext = xasprintf ("%s\n%s", msg, help);
- po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, true,
- msgext);
- free (msgext);
- free (help);
- }
- else
- po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
-
- exit_status = EXIT_FAILURE;
- }
- plural_expr = args.res;
-
- /* See whether nplurals and plural fit together. */
- if (exit_status != EXIT_FAILURE)
- check_plural_eval (plural_expr, nplurals_value, header);
-
- /* Check the number of plurals of the translations. */
- if (exit_status != EXIT_FAILURE)
- {
- if (min_nplurals < nplurals_value)
- {
- char *msg1 =
- xasprintf (_("nplurals = %lu"), nplurals_value);
- char *msg2 =
- xasprintf (ngettext ("but some messages have only one plural form",
- "but some messages have only %lu plural forms",
- min_nplurals),
- min_nplurals);
- po_xerror2 (PO_SEVERITY_ERROR,
- header, NULL, 0, 0, false, msg1,
- min_pos, NULL, 0, 0, false, msg2);
- free (msg2);
- free (msg1);
- exit_status = EXIT_FAILURE;
- }
- else if (max_nplurals > nplurals_value)
- {
- char *msg1 =
- xasprintf (_("nplurals = %lu"), nplurals_value);
- char *msg2 =
- xasprintf (ngettext ("but some messages have one plural form",
- "but some messages have %lu plural forms",
- max_nplurals),
- max_nplurals);
- po_xerror2 (PO_SEVERITY_ERROR,
- header, NULL, 0, 0, false, msg1,
- max_pos, NULL, 0, 0, false, msg2);
- free (msg2);
- free (msg1);
- exit_status = EXIT_FAILURE;
- }
- /* The only valid case is max_nplurals <= n <= min_nplurals,
- which means either has_plural == NULL or
- max_nplurals = n = min_nplurals. */
- }
- }
- }
- else if (has_plural != NULL)
- {
- po_xerror (PO_SEVERITY_ERROR, has_plural, NULL, 0, 0, false,
- _("message catalog has plural form translations, but lacks a header entry with \"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\""));
- exit_status = EXIT_FAILURE;
- }
-}
-
-
-/* Signal an error when checking format strings. */
-static const message_ty *curr_mp;
-static lex_pos_ty curr_msgid_pos;
-static void
-formatstring_error_logger (const char *format, ...)
-{
- va_list args;
- char *msg;
-
- va_start (args, format);
- if (vasprintf (&msg, format, args) < 0)
- error (EXIT_FAILURE, 0, _("memory exhausted"));
- va_end (args);
- po_xerror (PO_SEVERITY_ERROR,
- curr_mp, curr_msgid_pos.file_name, curr_msgid_pos.line_number,
- (size_t)(-1), false, msg);
- free (msg);
-}
-
-
-/* Perform miscellaneous checks on a message. */
-static void
-check_pair (const message_ty *mp,
- 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;
- unsigned int j;
- const char *p;
-
- /* If the msgid string is empty we have the special entry reserved for
- information about the translation. */
- if (msgid[0] == '\0')
- return;
-
- /* Test 1: check whether all or none of the strings begin with a '\n'. */
- has_newline = (msgid[0] == '\n');
-#define TEST_NEWLINE(p) (p[0] == '\n')
- if (msgid_plural != NULL)
- {
- if (TEST_NEWLINE(msgid_plural) != has_newline)
- {
- po_xerror (PO_SEVERITY_ERROR,
- mp, msgid_pos->file_name, msgid_pos->line_number,
- (size_t)(-1), false, _("\
-`msgid' and `msgid_plural' entries do not both begin with '\\n'"));
- exit_status = EXIT_FAILURE;
- }
- for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++)
- if (TEST_NEWLINE(p) != has_newline)
- {
- char *msg =
- xasprintf (_("\
-`msgid' and `msgstr[%u]' entries do not both begin with '\\n'"), j);
- po_xerror (PO_SEVERITY_ERROR,
- mp, msgid_pos->file_name, msgid_pos->line_number,
- (size_t)(-1), false, msg);
- free (msg);
- exit_status = EXIT_FAILURE;
- }
- }
- else
- {
- if (TEST_NEWLINE(msgstr) != has_newline)
- {
- po_xerror (PO_SEVERITY_ERROR,
- mp, msgid_pos->file_name, msgid_pos->line_number,
- (size_t)(-1), false, _("\
-`msgid' and `msgstr' entries do not both begin with '\\n'"));
- exit_status = EXIT_FAILURE;
- }
- }
-#undef TEST_NEWLINE
-
- /* Test 2: check whether all or none of the strings end with a '\n'. */
- 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)
- {
- po_xerror (PO_SEVERITY_ERROR,
- mp, msgid_pos->file_name, msgid_pos->line_number,
- (size_t)(-1), false, _("\
-`msgid' and `msgid_plural' entries do not both end with '\\n'"));
- exit_status = EXIT_FAILURE;
- }
- for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++)
- if (TEST_NEWLINE(p) != has_newline)
- {
- char *msg =
- xasprintf (_("\
-`msgid' and `msgstr[%u]' entries do not both end with '\\n'"), j);
- po_xerror (PO_SEVERITY_ERROR,
- mp, msgid_pos->file_name, msgid_pos->line_number,
- (size_t)(-1), false, msg);
- free (msg);
- exit_status = EXIT_FAILURE;
- }
- }
- else
- {
- if (TEST_NEWLINE(msgstr) != has_newline)
- {
- po_xerror (PO_SEVERITY_ERROR,
- mp, msgid_pos->file_name, msgid_pos->line_number,
- (size_t)(-1), false, _("\
-`msgid' and `msgstr' entries do not both end with '\\n'"));
- exit_status = EXIT_FAILURE;
- }
- }
-#undef TEST_NEWLINE
-
- if (check_compatibility && msgid_plural != NULL)
- {
- po_xerror (PO_SEVERITY_ERROR,
- mp, msgid_pos->file_name, msgid_pos->line_number,
- (size_t)(-1), false, _("\
-plural handling is a GNU gettext extension"));
- exit_status = EXIT_FAILURE;
- }
-
- if (check_format_strings)
- /* Test 3: Check whether both formats strings contain the same number
- of format specifications. */
- {
- curr_mp = mp;
- curr_msgid_pos = *msgid_pos;
- if (check_msgid_msgstr_format (msgid, msgid_plural, msgstr, msgstr_len,
- is_format, formatstring_error_logger))
- exit_status = EXIT_FAILURE;
- }
-
- if (check_accelerators && msgid_plural == NULL)
- /* Test 4: Check that if msgid is a menu item with a keyboard accelerator,
- the msgstr has an accelerator as well. A keyboard accelerator is
- designated by an immediately preceding '&'. We cannot check whether
- two accelerators collide, only whether the translator has bothered
- thinking about them. */
- {
- const char *p;
-
- /* We are only interested in msgids that contain exactly one '&'. */
- p = strchr (msgid, accelerator_char);
- if (p != NULL && strchr (p + 1, accelerator_char) == NULL)
- {
- /* Count the number of '&' in msgstr, but ignore '&&'. */
- 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)
- {
- char *msg =
- xasprintf (_("msgstr lacks the keyboard accelerator mark '%c'"),
- accelerator_char);
- po_xerror (PO_SEVERITY_ERROR,
- mp, msgid_pos->file_name, msgid_pos->line_number,
- (size_t)(-1), false, msg);
- free (msg);
- }
- else if (count > 1)
- {
- char *msg =
- xasprintf (_("msgstr has too many keyboard accelerator marks '%c'"),
- accelerator_char);
- po_xerror (PO_SEVERITY_ERROR,
- mp, msgid_pos->file_name, msgid_pos->line_number,
- (size_t)(-1), false, msg);
- free (msg);
- }
- }
- }
-}
-
-
-/* Perform miscellaneous checks on a header entry. */
-static void
-check_header_entry (const message_ty *mp, 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)
- {
- char *msg =
- xasprintf (_("headerfield `%s' missing in header\n"),
- required_fields[cnt]);
- po_xerror (PO_SEVERITY_ERROR, mp, gram_pos.file_name, (size_t)(-1),
- (size_t)(-1), true, msg);
- free (msg);
- }
- else if (endp != msgstr_string && endp[-1] != '\n')
- {
- char *msg =
- xasprintf (_("\
-header field `%s' should start at beginning of line\n"),
- required_fields[cnt]);
- po_xerror (PO_SEVERITY_ERROR, mp, gram_pos.file_name, (size_t)(-1),
- (size_t)(-1), true, msg);
- free (msg);
- }
- else if (default_values[cnt] != NULL
- && strncmp (default_values[cnt],
- endp + strlen (required_fields[cnt]) + 2,
- strlen (default_values[cnt])) == 0)
- {
- if (initial != -1)
- {
- po_xerror (PO_SEVERITY_ERROR,
- mp, gram_pos.file_name, (size_t)(-1), (size_t)(-1),
- true, _("\
-some header fields still have the initial default value\n"));
- initial = -1;
- break;
- }
- else
- initial = cnt;
- }
- }
-
- if (initial != -1)
- {
- char *msg =
- xasprintf (_("field `%s' still has initial default value\n"),
- required_fields[initial]);
- po_xerror (PO_SEVERITY_ERROR, mp, gram_pos.file_name, (size_t)(-1),
- (size_t)(-1), true, msg);
- free (msg);
- }
-}
-
-
/* The rest of the file defines a subclass msgfmt_po_reader_ty of
default_po_reader_ty. Its particularities are:
- The header entry check is performed on-the-fly.
@@ -1644,10 +977,6 @@ msgfmt_frob_new_message (default_po_reader_ty *that, message_ty *mp,
this->has_header_entry = true;
if (!mp->is_fuzzy)
this->has_nonfuzzy_header_entry = true;
-
- /* Do some more tests on the contents of the header entry. */
- if (check_header)
- check_header_entry (mp, mp->msgstr);
}
else
/* We don't count the header entry in the statistic so place
@@ -1657,11 +986,11 @@ msgfmt_frob_new_message (default_po_reader_ty *that, message_ty *mp,
else
++msgs_translated;
- /* Do some more checks on both strings. */
- check_pair (mp,
- mp->msgid, msgid_pos, mp->msgid_plural,
- mp->msgstr, mp->msgstr_len, msgstr_pos,
- mp->is_format);
+ if (check_message (mp, msgid_pos, msgstr_pos,
+ 1, check_format_strings, check_header,
+ check_compatibility,
+ check_accelerators, accelerator_char))
+ exit_status = EXIT_FAILURE;
}
}
}
diff --git a/gettext-tools/src/msgl-check.c b/gettext-tools/src/msgl-check.c
new file mode 100644
index 0000000..619b6a2
--- /dev/null
+++ b/gettext-tools/src/msgl-check.c
@@ -0,0 +1,740 @@
+/* Checking of messages in PO files.
+ Copyright (C) 1995-1998, 2000-2005 Free Software Foundation, Inc.
+ Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+/* Specification. */
+#include "msgl-check.h"
+
+#include <ctype.h>
+#include <limits.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "xerror.h"
+#include "po-xerror.h"
+#include "format.h"
+#include "plural-exp.h"
+#include "plural-table.h"
+#include "strstr.h"
+#include "vasprintf.h"
+#include "exit.h"
+#include "message.h"
+#include "gettext.h"
+
+#define _(str) gettext (str)
+
+#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
+
+/* Some platforms don't have the sigjmp_buf type in <setjmp.h>. */
+#if defined _MSC_VER || defined __MINGW32__
+/* Native Woe32 API. */
+# define sigjmp_buf jmp_buf
+# define sigsetjmp(env,savesigs) setjmp (env)
+# define siglongjmp longjmp
+#endif
+
+/* We use siginfo to get precise information about the signal.
+ But siginfo doesn't work on Irix 6.5. */
+#if HAVE_SIGINFO && !defined (__sgi)
+# define USE_SIGINFO 1
+#endif
+
+
+static sigjmp_buf sigfpe_exit;
+
+#if USE_SIGINFO
+
+static int sigfpe_code;
+
+/* Signal handler called in case of arithmetic exception (e.g. division
+ by zero) during plural_eval. */
+static void
+sigfpe_handler (int sig, siginfo_t *sip, void *scp)
+{
+ sigfpe_code = sip->si_code;
+ siglongjmp (sigfpe_exit, 1);
+}
+
+#else
+
+/* Signal handler called in case of arithmetic exception (e.g. division
+ by zero) during plural_eval. */
+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
+}
+
+/* Check the values returned by plural_eval. */
+static int
+check_plural_eval (struct expression *plural_expr,
+ unsigned long nplurals_value,
+ const message_ty *header)
+{
+ if (sigsetjmp (sigfpe_exit, 1) == 0)
+ {
+ unsigned long n;
+
+ /* Protect against arithmetic exceptions. */
+ install_sigfpe_handler ();
+
+ for (n = 0; n <= 1000; n++)
+ {
+ unsigned long val = plural_eval (plural_expr, n);
+
+ if ((long) val < 0)
+ {
+ /* End of protection against arithmetic exceptions. */
+ uninstall_sigfpe_handler ();
+
+ po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false,
+ _("plural expression can produce negative values"));
+ return 1;
+ }
+ else if (val >= nplurals_value)
+ {
+ char *msg;
+
+ /* End of protection against arithmetic exceptions. */
+ uninstall_sigfpe_handler ();
+
+ msg = xasprintf (_("nplurals = %lu but plural expression can produce values as large as %lu"),
+ nplurals_value, val);
+ po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
+ free (msg);
+ return 1;
+ }
+ }
+
+ /* End of protection against arithmetic exceptions. */
+ uninstall_sigfpe_handler ();
+
+ return 0;
+ }
+ else
+ {
+ /* Caught an arithmetic exception. */
+ const char *msg;
+
+ /* End of protection against arithmetic exceptions. */
+ 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");
+ }
+
+ po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
+ return 1;
+ }
+}
+
+
+/* Try to help the translator by looking up the right plural formula for her.
+ Return a freshly allocated multiline help string, or NULL. */
+static char *
+plural_help (const char *nullentry)
+{
+ const char *language;
+ size_t j;
+
+ 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 *helpline1 =
+ xasprintf (_("Try using the following, valid for %s:"),
+ plural_table[j].language);
+ char *help =
+ xasprintf ("%s\n\"Plural-Forms: %s\\n\"\n",
+ helpline1, plural_table[j].value);
+ free (helpline1);
+ return help;
+ }
+ }
+ return NULL;
+}
+
+
+/* Perform plural expression checking.
+ Return nonzero if an error was seen. */
+int
+check_plural (message_list_ty *mlp)
+{
+ int seen_error = 0;
+ const message_ty *has_plural;
+ unsigned long min_nplurals;
+ const message_ty *min_pos;
+ unsigned long max_nplurals;
+ const message_ty *max_pos;
+ size_t j;
+ message_ty *header;
+
+ /* Determine whether mlp has plural entries. */
+ 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;
+
+ 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;
+ }
+ if (max_nplurals < n)
+ {
+ max_nplurals = n;
+ max_pos = mp;
+ }
+ }
+ }
+
+ /* Look at the plural entry for this domain.
+ Cf, function extract_plural_expression. */
+ header = message_list_search (mlp, "");
+ if (header != NULL)
+ {
+ const char *nullentry;
+ const char *plural;
+ const char *nplurals;
+
+ nullentry = header->msgstr;
+
+ plural = strstr (nullentry, "plural=");
+ nplurals = strstr (nullentry, "nplurals=");
+ if (plural == NULL && has_plural != NULL)
+ {
+ const char *msg1 =
+ _("message catalog has plural form translations");
+ const char *msg2 =
+ _("but header entry lacks a \"plural=EXPRESSION\" attribute");
+ char *help = plural_help (nullentry);
+
+ if (help != NULL)
+ {
+ char *msg2ext = xasprintf ("%s\n%s", msg2, help);
+ po_xerror2 (PO_SEVERITY_ERROR,
+ has_plural, NULL, 0, 0, false, msg1,
+ header, NULL, 0, 0, true, msg2ext);
+ free (msg2ext);
+ free (help);
+ }
+ else
+ po_xerror2 (PO_SEVERITY_ERROR,
+ has_plural, NULL, 0, 0, false, msg1,
+ header, NULL, 0, 0, false, msg2);
+
+ seen_error = 1;
+ }
+ if (nplurals == NULL && has_plural != NULL)
+ {
+ const char *msg1 =
+ _("message catalog has plural form translations");
+ const char *msg2 =
+ _("but header entry lacks a \"nplurals=INTEGER\" attribute");
+ char *help = plural_help (nullentry);
+
+ if (help != NULL)
+ {
+ char *msg2ext = xasprintf ("%s\n%s", msg2, help);
+ po_xerror2 (PO_SEVERITY_ERROR,
+ has_plural, NULL, 0, 0, false, msg1,
+ header, NULL, 0, 0, true, msg2ext);
+ free (msg2ext);
+ free (help);
+ }
+ else
+ po_xerror2 (PO_SEVERITY_ERROR,
+ has_plural, NULL, 0, 0, false, msg1,
+ header, NULL, 0, 0, false, msg2);
+
+ seen_error = 1;
+ }
+ if (plural != NULL && nplurals != NULL)
+ {
+ const char *endp;
+ unsigned long int nplurals_value;
+ struct parse_args args;
+ struct expression *plural_expr;
+
+ /* First check the number. */
+ 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)
+ {
+ const char *msg = _("invalid nplurals value");
+ char *help = plural_help (nullentry);
+
+ if (help != NULL)
+ {
+ char *msgext = xasprintf ("%s\n%s", msg, help);
+ po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, true,
+ msgext);
+ free (msgext);
+ free (help);
+ }
+ else
+ po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
+
+ seen_error = 1;
+ }
+
+ /* Then check the expression. */
+ plural += 7;
+ args.cp = plural;
+ if (parse_plural_expression (&args) != 0)
+ {
+ const char *msg = _("invalid plural expression");
+ char *help = plural_help (nullentry);
+
+ if (help != NULL)
+ {
+ char *msgext = xasprintf ("%s\n%s", msg, help);
+ po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, true,
+ msgext);
+ free (msgext);
+ free (help);
+ }
+ else
+ po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
+
+ seen_error = 1;
+ }
+ plural_expr = args.res;
+
+ /* See whether nplurals and plural fit together. */
+ if (!seen_error)
+ seen_error = check_plural_eval (plural_expr, nplurals_value, header);
+
+ /* Check the number of plurals of the translations. */
+ if (!seen_error)
+ {
+ if (min_nplurals < nplurals_value)
+ {
+ char *msg1 =
+ xasprintf (_("nplurals = %lu"), nplurals_value);
+ char *msg2 =
+ xasprintf (ngettext ("but some messages have only one plural form",
+ "but some messages have only %lu plural forms",
+ min_nplurals),
+ min_nplurals);
+ po_xerror2 (PO_SEVERITY_ERROR,
+ header, NULL, 0, 0, false, msg1,
+ min_pos, NULL, 0, 0, false, msg2);
+ free (msg2);
+ free (msg1);
+ seen_error = 1;
+ }
+ else if (max_nplurals > nplurals_value)
+ {
+ char *msg1 =
+ xasprintf (_("nplurals = %lu"), nplurals_value);
+ char *msg2 =
+ xasprintf (ngettext ("but some messages have one plural form",
+ "but some messages have %lu plural forms",
+ max_nplurals),
+ max_nplurals);
+ po_xerror2 (PO_SEVERITY_ERROR,
+ header, NULL, 0, 0, false, msg1,
+ max_pos, NULL, 0, 0, false, msg2);
+ free (msg2);
+ free (msg1);
+ seen_error = 1;
+ }
+ /* The only valid case is max_nplurals <= n <= min_nplurals,
+ which means either has_plural == NULL or
+ max_nplurals = n = min_nplurals. */
+ }
+ }
+ }
+ else if (has_plural != NULL)
+ {
+ po_xerror (PO_SEVERITY_ERROR, has_plural, NULL, 0, 0, false,
+ _("message catalog has plural form translations, but lacks a header entry with \"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\""));
+ seen_error = 1;
+ }
+
+ return seen_error;
+}
+
+
+/* Signal an error when checking format strings. */
+static const message_ty *curr_mp;
+static lex_pos_ty curr_msgid_pos;
+static void
+formatstring_error_logger (const char *format, ...)
+{
+ va_list args;
+ char *msg;
+
+ va_start (args, format);
+ if (vasprintf (&msg, format, args) < 0)
+ error (EXIT_FAILURE, 0, _("memory exhausted"));
+ va_end (args);
+ po_xerror (PO_SEVERITY_ERROR,
+ curr_mp, curr_msgid_pos.file_name, curr_msgid_pos.line_number,
+ (size_t)(-1), false, msg);
+ free (msg);
+}
+
+
+/* Perform miscellaneous checks on a message. */
+static int
+check_pair (const message_ty *mp,
+ 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,
+ const enum is_format is_format[NFORMATS],
+ int check_newlines,
+ int check_format_strings,
+ int check_compatibility,
+ int check_accelerators, char accelerator_char)
+{
+ int seen_error;
+ int has_newline;
+ unsigned int j;
+ const char *p;
+
+ /* If the msgid string is empty we have the special entry reserved for
+ information about the translation. */
+ if (msgid[0] == '\0')
+ return 0;
+
+ seen_error = 0;
+
+ if (check_newlines)
+ {
+ /* Test 1: check whether all or none of the strings begin with a '\n'. */
+ has_newline = (msgid[0] == '\n');
+#define TEST_NEWLINE(p) (p[0] == '\n')
+ if (msgid_plural != NULL)
+ {
+ if (TEST_NEWLINE(msgid_plural) != has_newline)
+ {
+ po_xerror (PO_SEVERITY_ERROR,
+ mp, msgid_pos->file_name, msgid_pos->line_number,
+ (size_t)(-1), false, _("\
+`msgid' and `msgid_plural' entries do not both begin with '\\n'"));
+ seen_error = 1;
+ }
+ for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++)
+ if (TEST_NEWLINE(p) != has_newline)
+ {
+ char *msg =
+ xasprintf (_("\
+`msgid' and `msgstr[%u]' entries do not both begin with '\\n'"), j);
+ po_xerror (PO_SEVERITY_ERROR,
+ mp, msgid_pos->file_name, msgid_pos->line_number,
+ (size_t)(-1), false, msg);
+ free (msg);
+ seen_error = 1;
+ }
+ }
+ else
+ {
+ if (TEST_NEWLINE(msgstr) != has_newline)
+ {
+ po_xerror (PO_SEVERITY_ERROR,
+ mp, msgid_pos->file_name, msgid_pos->line_number,
+ (size_t)(-1), false, _("\
+`msgid' and `msgstr' entries do not both begin with '\\n'"));
+ seen_error = 1;
+ }
+ }
+#undef TEST_NEWLINE
+
+ /* Test 2: check whether all or none of the strings end with a '\n'. */
+ 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)
+ {
+ po_xerror (PO_SEVERITY_ERROR,
+ mp, msgid_pos->file_name, msgid_pos->line_number,
+ (size_t)(-1), false, _("\
+`msgid' and `msgid_plural' entries do not both end with '\\n'"));
+ seen_error = 1;
+ }
+ for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++)
+ if (TEST_NEWLINE(p) != has_newline)
+ {
+ char *msg =
+ xasprintf (_("\
+`msgid' and `msgstr[%u]' entries do not both end with '\\n'"), j);
+ po_xerror (PO_SEVERITY_ERROR,
+ mp, msgid_pos->file_name, msgid_pos->line_number,
+ (size_t)(-1), false, msg);
+ free (msg);
+ seen_error = 1;
+ }
+ }
+ else
+ {
+ if (TEST_NEWLINE(msgstr) != has_newline)
+ {
+ po_xerror (PO_SEVERITY_ERROR,
+ mp, msgid_pos->file_name, msgid_pos->line_number,
+ (size_t)(-1), false, _("\
+`msgid' and `msgstr' entries do not both end with '\\n'"));
+ seen_error = 1;
+ }
+ }
+#undef TEST_NEWLINE
+ }
+
+ if (check_compatibility && msgid_plural != NULL)
+ {
+ po_xerror (PO_SEVERITY_ERROR,
+ mp, msgid_pos->file_name, msgid_pos->line_number,
+ (size_t)(-1), false, _("\
+plural handling is a GNU gettext extension"));
+ seen_error = 1;
+ }
+
+ if (check_format_strings)
+ /* Test 3: Check whether both formats strings contain the same number
+ of format specifications. */
+ {
+ curr_mp = mp;
+ curr_msgid_pos = *msgid_pos;
+ if (check_msgid_msgstr_format (msgid, msgid_plural, msgstr, msgstr_len,
+ is_format, formatstring_error_logger))
+ seen_error = 1;
+ }
+
+ if (check_accelerators && msgid_plural == NULL)
+ /* Test 4: Check that if msgid is a menu item with a keyboard accelerator,
+ the msgstr has an accelerator as well. A keyboard accelerator is
+ designated by an immediately preceding '&'. We cannot check whether
+ two accelerators collide, only whether the translator has bothered
+ thinking about them. */
+ {
+ const char *p;
+
+ /* We are only interested in msgids that contain exactly one '&'. */
+ p = strchr (msgid, accelerator_char);
+ if (p != NULL && strchr (p + 1, accelerator_char) == NULL)
+ {
+ /* Count the number of '&' in msgstr, but ignore '&&'. */
+ 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)
+ {
+ char *msg =
+ xasprintf (_("msgstr lacks the keyboard accelerator mark '%c'"),
+ accelerator_char);
+ po_xerror (PO_SEVERITY_ERROR,
+ mp, msgid_pos->file_name, msgid_pos->line_number,
+ (size_t)(-1), false, msg);
+ free (msg);
+ }
+ else if (count > 1)
+ {
+ char *msg =
+ xasprintf (_("msgstr has too many keyboard accelerator marks '%c'"),
+ accelerator_char);
+ po_xerror (PO_SEVERITY_ERROR,
+ mp, msgid_pos->file_name, msgid_pos->line_number,
+ (size_t)(-1), false, msg);
+ free (msg);
+ }
+ }
+ }
+
+ return seen_error;
+}
+
+
+/* Perform miscellaneous checks on a header entry. */
+static void
+check_header_entry (const message_ty *mp, 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)
+ {
+ char *msg =
+ xasprintf (_("headerfield `%s' missing in header\n"),
+ required_fields[cnt]);
+ po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, true, msg);
+ free (msg);
+ }
+ else if (endp != msgstr_string && endp[-1] != '\n')
+ {
+ char *msg =
+ xasprintf (_("\
+header field `%s' should start at beginning of line\n"),
+ required_fields[cnt]);
+ po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, true, msg);
+ free (msg);
+ }
+ else if (default_values[cnt] != NULL
+ && strncmp (default_values[cnt],
+ endp + strlen (required_fields[cnt]) + 2,
+ strlen (default_values[cnt])) == 0)
+ {
+ if (initial != -1)
+ {
+ po_xerror (PO_SEVERITY_ERROR,
+ mp, NULL, 0, 0, true, _("\
+some header fields still have the initial default value\n"));
+ initial = -1;
+ break;
+ }
+ else
+ initial = cnt;
+ }
+ }
+
+ if (initial != -1)
+ {
+ char *msg =
+ xasprintf (_("field `%s' still has initial default value\n"),
+ required_fields[initial]);
+ po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, true, msg);
+ free (msg);
+ }
+}
+
+
+/* Perform all checks on a message.
+ Return nonzero if an error was seen. */
+int
+check_message (const message_ty *mp,
+ const lex_pos_ty *msgid_pos, const lex_pos_ty *msgstr_pos,
+ int check_newlines,
+ int check_format_strings,
+ int check_header,
+ int check_compatibility,
+ int check_accelerators, char accelerator_char)
+{
+ if (check_header && mp->msgid[0] == '\0')
+ check_header_entry (mp, mp->msgstr);
+
+ return check_pair (mp,
+ mp->msgid, msgid_pos, mp->msgid_plural,
+ mp->msgstr, mp->msgstr_len, msgstr_pos,
+ mp->is_format,
+ check_newlines, check_format_strings, check_compatibility,
+ check_accelerators, accelerator_char);
+}
diff --git a/gettext-tools/src/msgl-check.h b/gettext-tools/src/msgl-check.h
new file mode 100644
index 0000000..4fabcda
--- /dev/null
+++ b/gettext-tools/src/msgl-check.h
@@ -0,0 +1,51 @@
+/* Checking of messages in PO files.
+ Copyright (C) 2005 Free Software Foundation, Inc.
+ Written by Bruno Haible <bruno@clisp.org>, 2005.
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2, or (at your option)
+ any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software Foundation,
+ Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
+
+#ifndef _MSGL_CHECK_H
+#define _MSGL_CHECK_H 1
+
+#include "message.h"
+#include "pos.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/* Perform plural expression checking.
+ Return nonzero if an error was seen. */
+extern int check_plural (message_list_ty *mlp);
+
+/* Perform all checks on a message.
+ Return nonzero if an error was seen. */
+extern int check_message (const message_ty *mp,
+ const lex_pos_ty *msgid_pos,
+ const lex_pos_ty *msgstr_pos,
+ int check_newlines,
+ int check_format_strings,
+ int check_header,
+ int check_compatibility,
+ int check_accelerators, char accelerator_char);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _MSGL_CHECK_H */