diff options
-rw-r--r-- | gettext-tools/src/ChangeLog | 27 | ||||
-rw-r--r-- | gettext-tools/src/FILES | 12 | ||||
-rw-r--r-- | gettext-tools/src/Makefile.am | 9 | ||||
-rw-r--r-- | gettext-tools/src/format.c | 4 | ||||
-rw-r--r-- | gettext-tools/src/format.h | 4 | ||||
-rw-r--r-- | gettext-tools/src/gettext-po.c | 170 | ||||
-rw-r--r-- | gettext-tools/src/gettext-po.h | 24 | ||||
-rw-r--r-- | gettext-tools/src/msgfmt.c | 687 | ||||
-rw-r--r-- | gettext-tools/src/msgl-check.c | 740 | ||||
-rw-r--r-- | gettext-tools/src/msgl-check.h | 51 |
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 */ |