]> git.ipfire.org Git - thirdparty/gettext.git/commitdiff
Extract the checking functionality of msgfmt, and make it usable
authorBruno Haible <bruno@clisp.org>
Mon, 19 Sep 2005 16:10:25 +0000 (16:10 +0000)
committerBruno Haible <bruno@clisp.org>
Tue, 23 Jun 2009 10:12:50 +0000 (12:12 +0200)
through libgettextpo.

gettext-tools/src/ChangeLog
gettext-tools/src/FILES
gettext-tools/src/Makefile.am
gettext-tools/src/format.c
gettext-tools/src/format.h
gettext-tools/src/gettext-po.c
gettext-tools/src/gettext-po.h
gettext-tools/src/msgfmt.c
gettext-tools/src/msgl-check.c [new file with mode: 0644]
gettext-tools/src/msgl-check.h [new file with mode: 0644]

index 76a410dabc5d653e9085c06fe093255a71a09892..3a0f1937b0916d1cb73d91846035760c8ae481a3 100644 (file)
@@ -1,3 +1,30 @@
+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.
index 0ab3c8f6d7cd792326d1dc0e7f22b2f974ba70f6..d812d49d3625d9f554d860c6475a34ddca30c2d5 100644 (file)
@@ -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.
index 46a882c9bbdb30085935db871a398db814f0c456..375469dea9096cdeb6e57e5ae75a5c05a6c1c076 100644 (file)
@@ -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
index dcabc32c41dffec63f37af367cfa50ce18c57120..9c0b11e486f7f48e33e0c1838105f1ef2b020405 100644 (file)
@@ -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;
index ea5b223eb0c0747886a4ed4c173131b93c1ff109..80a609a2feb3c5056694eaaba4ea4d44d6a77b46 100644 (file)
@@ -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);
 
 
index 5efcdd584a86cd6cb5538451c61842d42ffb18aa..1f9bc40b9cb3da62cfcf806fe119b160e9c289e6 100644 (file)
@@ -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;
-}
index c3927be01cacaff71ccdde9fa685b02641149023..0f215c23b5abc6459c2b96fe2c7f8f58a8f2258d 100644 (file)
@@ -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
index 3fd3fcd7b2b71687846ce0dcaa822ca386234379..d9d315233a5d1f28b4775eb50a6f1bb4d4016954 100644 (file)
 #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"
 #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"
 #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 (file)
index 0000000..619b6a2
--- /dev/null
@@ -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 (file)
index 0000000..4fabcda
--- /dev/null
@@ -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 */