facf0c92e0
Red Bear OS is a full fork. All sources must be available from git clone with zero network access. Removed gitignore rules that excluded fetched source trees under recipes/*/source/, local/recipes/kde/*/source/, local/recipes/qt/*/source/, and vendor source trees. Build artifacts (target/, build/, source.tar, *.o, *.so) remain excluded. 127291 files added — kernel, relibc, base, bootloader, pkgar, all KDE/Qt frameworks, mesa, wayland, DRM drivers, and every other recipe source.
1222 lines
36 KiB
C
1222 lines
36 KiB
C
/* Checking of messages in PO files.
|
|
Copyright (C) 1995-1998, 2000-2008, 2010-2016, 2019 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 3 of the License, 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, see <https://www.gnu.org/licenses/>. */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
/* Specification. */
|
|
#include "msgl-check.h"
|
|
|
|
#include <limits.h>
|
|
#include <setjmp.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdarg.h>
|
|
|
|
#include "c-ctype.h"
|
|
#include "xalloc.h"
|
|
#include "xvasprintf.h"
|
|
#include "po-xerror.h"
|
|
#include "format.h"
|
|
#include "plural-exp.h"
|
|
#include "plural-eval.h"
|
|
#include "plural-table.h"
|
|
#include "c-strstr.h"
|
|
#include "message.h"
|
|
#include "quote.h"
|
|
#include "sentence.h"
|
|
#include "unictype.h"
|
|
#include "unistr.h"
|
|
#include "gettext.h"
|
|
|
|
#define _(str) gettext (str)
|
|
|
|
#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
|
|
|
|
|
|
/* Evaluates the plural formula for min <= n <= max
|
|
and returns the estimated number of times the value j was assumed. */
|
|
static unsigned int
|
|
plural_expression_histogram (const struct plural_distribution *self,
|
|
int min, int max, unsigned long j)
|
|
{
|
|
if (min < 0)
|
|
min = 0;
|
|
/* Limit the number of evaluations. Nothing interesting happens beyond
|
|
1000. */
|
|
if (max - min > 1000)
|
|
max = min + 1000;
|
|
if (min <= max)
|
|
{
|
|
const struct expression *expr = self->expr;
|
|
unsigned long n;
|
|
unsigned int count;
|
|
|
|
/* Protect against arithmetic exceptions. */
|
|
install_sigfpe_handler ();
|
|
|
|
count = 0;
|
|
for (n = min; n <= max; n++)
|
|
{
|
|
unsigned long val = plural_eval (expr, n);
|
|
|
|
if (val == j)
|
|
count++;
|
|
}
|
|
|
|
/* End of protection against arithmetic exceptions. */
|
|
uninstall_sigfpe_handler ();
|
|
|
|
return count;
|
|
}
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Check the values returned by plural_eval.
|
|
Signals the errors through po_xerror.
|
|
Return the number of errors that were seen.
|
|
If no errors, returns in *DISTRIBUTION information about the plural_eval
|
|
values distribution. */
|
|
int
|
|
check_plural_eval (const struct expression *plural_expr,
|
|
unsigned long nplurals_value,
|
|
const message_ty *header,
|
|
struct plural_distribution *distribution)
|
|
{
|
|
/* Do as if the plural formula assumes a value N infinitely often if it
|
|
assumes it at least 5 times. */
|
|
#define OFTEN 5
|
|
unsigned char * volatile array;
|
|
|
|
/* Allocate a distribution array. */
|
|
if (nplurals_value <= 100)
|
|
array = XCALLOC (nplurals_value, unsigned char);
|
|
else
|
|
/* nplurals_value is nonsense. Don't risk an out-of-memory. */
|
|
array = NULL;
|
|
|
|
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"));
|
|
free (array);
|
|
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);
|
|
free (array);
|
|
return 1;
|
|
}
|
|
|
|
if (array != NULL && array[val] < OFTEN)
|
|
array[val]++;
|
|
}
|
|
|
|
/* End of protection against arithmetic exceptions. */
|
|
uninstall_sigfpe_handler ();
|
|
|
|
/* Normalize the array[val] statistics. */
|
|
if (array != NULL)
|
|
{
|
|
unsigned long val;
|
|
|
|
for (val = 0; val < nplurals_value; val++)
|
|
array[val] = (array[val] == OFTEN ? 1 : 0);
|
|
}
|
|
|
|
distribution->expr = plural_expr;
|
|
distribution->often = array;
|
|
distribution->often_length = (array != NULL ? nplurals_value : 0);
|
|
distribution->histogram = plural_expression_histogram;
|
|
|
|
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);
|
|
|
|
free (array);
|
|
|
|
return 1;
|
|
}
|
|
#undef OFTEN
|
|
}
|
|
|
|
|
|
/* 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)
|
|
{
|
|
struct plural_table_entry *ptentry = NULL;
|
|
|
|
{
|
|
const char *language;
|
|
|
|
language = c_strstr (nullentry, "Language: ");
|
|
if (language != NULL)
|
|
{
|
|
size_t len;
|
|
|
|
language += 10;
|
|
len = strcspn (language, " \t\n");
|
|
if (len > 0)
|
|
{
|
|
size_t j;
|
|
|
|
for (j = 0; j < plural_table_size; j++)
|
|
if (len == strlen (plural_table[j].lang)
|
|
&& strncmp (language, plural_table[j].lang, len) == 0)
|
|
{
|
|
ptentry = &plural_table[j];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ptentry == NULL)
|
|
{
|
|
const char *language;
|
|
|
|
language = c_strstr (nullentry, "Language-Team: ");
|
|
if (language != NULL)
|
|
{
|
|
size_t j;
|
|
|
|
language += 15;
|
|
for (j = 0; j < plural_table_size; j++)
|
|
if (strncmp (language,
|
|
plural_table[j].language,
|
|
strlen (plural_table[j].language)) == 0)
|
|
{
|
|
ptentry = &plural_table[j];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ptentry != NULL)
|
|
{
|
|
char *helpline1 =
|
|
xasprintf (_("Try using the following, valid for %s:"),
|
|
ptentry->language);
|
|
char *help =
|
|
xasprintf ("%s\n\"Plural-Forms: %s\\n\"\n",
|
|
helpline1, ptentry->value);
|
|
free (helpline1);
|
|
return help;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* Perform plural expression checking.
|
|
Return the number of errors that were seen.
|
|
If no errors, returns in *DISTRIBUTION information about the plural_eval
|
|
values distribution. */
|
|
static int
|
|
check_plural (message_list_ty *mlp,
|
|
int ignore_untranslated_messages,
|
|
int ignore_fuzzy_messages,
|
|
struct plural_distribution *distributionp)
|
|
{
|
|
int seen_errors = 0;
|
|
const message_ty *has_plural;
|
|
unsigned long min_nplurals;
|
|
const message_ty *min_pos;
|
|
unsigned long max_nplurals;
|
|
const message_ty *max_pos;
|
|
struct plural_distribution distribution;
|
|
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;
|
|
distribution.expr = NULL;
|
|
distribution.often = NULL;
|
|
distribution.often_length = 0;
|
|
distribution.histogram = NULL;
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
{
|
|
message_ty *mp = mlp->item[j];
|
|
|
|
if (!mp->obsolete
|
|
&& !(ignore_untranslated_messages && mp->msgstr[0] == '\0')
|
|
&& !(ignore_fuzzy_messages && (mp->is_fuzzy && !is_header (mp)))
|
|
&& 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, NULL, "");
|
|
if (header != NULL && !header->obsolete)
|
|
{
|
|
const char *nullentry;
|
|
const char *plural;
|
|
const char *nplurals;
|
|
|
|
nullentry = header->msgstr;
|
|
|
|
plural = c_strstr (nullentry, "plural=");
|
|
nplurals = c_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_errors++;
|
|
}
|
|
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_errors++;
|
|
}
|
|
if (plural != NULL && nplurals != NULL)
|
|
{
|
|
const char *endp;
|
|
unsigned long int nplurals_value;
|
|
struct parse_args args;
|
|
const struct expression *plural_expr;
|
|
|
|
/* First check the number. */
|
|
nplurals += 9;
|
|
while (*nplurals != '\0' && c_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_errors++;
|
|
}
|
|
|
|
/* 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_errors++;
|
|
}
|
|
plural_expr = args.res;
|
|
|
|
/* See whether nplurals and plural fit together. */
|
|
if (!seen_errors)
|
|
seen_errors =
|
|
check_plural_eval (plural_expr, nplurals_value, header,
|
|
&distribution);
|
|
|
|
/* Check the number of plurals of the translations. */
|
|
if (!seen_errors)
|
|
{
|
|
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_errors++;
|
|
}
|
|
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_errors++;
|
|
}
|
|
/* The only valid case is max_nplurals <= n <= min_nplurals,
|
|
which means either has_plural == NULL or
|
|
max_nplurals = n = min_nplurals. */
|
|
}
|
|
}
|
|
else
|
|
goto no_plural;
|
|
}
|
|
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_errors++;
|
|
}
|
|
no_plural:
|
|
/* By default, the Germanic formula (n != 1) is used. */
|
|
distribution.expr = &germanic_plural;
|
|
{
|
|
unsigned char *array = XCALLOC (2, unsigned char);
|
|
array[1] = 1;
|
|
distribution.often = array;
|
|
}
|
|
distribution.often_length = 2;
|
|
distribution.histogram = plural_expression_histogram;
|
|
}
|
|
|
|
/* distribution is not needed if we report errors.
|
|
Also, if there was an error due to max_nplurals > nplurals_value,
|
|
we must not use distribution because we would be doing out-of-bounds
|
|
array accesses. */
|
|
if (seen_errors > 0)
|
|
free ((unsigned char *) distribution.often);
|
|
else
|
|
*distributionp = distribution;
|
|
|
|
return seen_errors;
|
|
}
|
|
|
|
|
|
/* 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, ...)
|
|
#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ > 2)
|
|
__attribute__ ((__format__ (__printf__, 1, 2)))
|
|
#endif
|
|
;
|
|
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.
|
|
PLURAL_DISTRIBUTION is either NULL or an array of nplurals elements,
|
|
PLURAL_DISTRIBUTION[j] being true if the value j appears to be assumed
|
|
infinitely often by the plural formula.
|
|
PLURAL_DISTRIBUTION_LENGTH is the length of the PLURAL_DISTRIBUTION
|
|
array. */
|
|
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 enum is_format is_format[NFORMATS],
|
|
int check_newlines,
|
|
int check_format_strings,
|
|
const struct plural_distribution *distribution,
|
|
int check_compatibility,
|
|
int check_accelerators, char accelerator_char)
|
|
{
|
|
int seen_errors;
|
|
int has_newline;
|
|
unsigned int j;
|
|
|
|
/* If the msgid string is empty we have the special entry reserved for
|
|
information about the translation. */
|
|
if (msgid[0] == '\0')
|
|
return 0;
|
|
|
|
seen_errors = 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)
|
|
{
|
|
const char *p;
|
|
|
|
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_errors++;
|
|
}
|
|
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_errors++;
|
|
}
|
|
}
|
|
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_errors++;
|
|
}
|
|
}
|
|
#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)
|
|
{
|
|
const char *p;
|
|
|
|
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_errors++;
|
|
}
|
|
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_errors++;
|
|
}
|
|
}
|
|
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_errors++;
|
|
}
|
|
}
|
|
#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_errors++;
|
|
}
|
|
|
|
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;
|
|
seen_errors +=
|
|
check_msgid_msgstr_format (msgid, msgid_plural, msgstr, msgstr_len,
|
|
is_format, mp->range, distribution,
|
|
formatstring_error_logger);
|
|
}
|
|
|
|
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);
|
|
seen_errors++;
|
|
}
|
|
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);
|
|
seen_errors++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return seen_errors;
|
|
}
|
|
|
|
|
|
/* Perform miscellaneous checks on a header entry. */
|
|
static int
|
|
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",
|
|
/* These are recommended but not yet required. */
|
|
"Language"
|
|
};
|
|
static const char *default_values[] =
|
|
{
|
|
"PACKAGE VERSION", "YEAR-MO-DA HO:MI+ZONE", "FULL NAME <EMAIL@ADDRESS>", "LANGUAGE <LL@li.org>", NULL,
|
|
"text/plain; charset=CHARSET", "ENCODING",
|
|
""
|
|
};
|
|
const size_t nfields = SIZEOF (required_fields);
|
|
/* FIXME: We could check if a required header field is missing and
|
|
report it as error. However, it's could be too rigorous and
|
|
break backward compatibility. */
|
|
#if 0
|
|
const size_t nrequiredfields = nfields - 1;
|
|
#endif
|
|
int seen_errors = 0;
|
|
int cnt;
|
|
|
|
for (cnt = 0; cnt < nfields; ++cnt)
|
|
{
|
|
#if 0
|
|
int severity =
|
|
(cnt < nrequiredfields ? PO_SEVERITY_ERROR : PO_SEVERITY_WARNING);
|
|
#else
|
|
int severity =
|
|
PO_SEVERITY_WARNING;
|
|
#endif
|
|
const char *field = required_fields[cnt];
|
|
size_t len = strlen (field);
|
|
const char *line;
|
|
|
|
for (line = msgstr_string; *line != '\0'; )
|
|
{
|
|
if (strncmp (line, field, len) == 0 && line[len] == ':')
|
|
{
|
|
const char *p = line + len + 1;
|
|
|
|
/* Test whether the field's value, starting at p, is the default
|
|
value. */
|
|
if (*p == ' ')
|
|
p++;
|
|
if (default_values[cnt] != NULL
|
|
&& strncmp (p, default_values[cnt],
|
|
strlen (default_values[cnt])) == 0)
|
|
{
|
|
p += strlen (default_values[cnt]);
|
|
if (*p == '\0' || *p == '\n')
|
|
{
|
|
char *msg =
|
|
xasprintf (_("header field '%s' still has the initial default value\n"),
|
|
field);
|
|
po_xerror (severity, mp, NULL, 0, 0, true, msg);
|
|
free (msg);
|
|
if (severity == PO_SEVERITY_ERROR)
|
|
seen_errors++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
line = strchrnul (line, '\n');
|
|
if (*line == '\n')
|
|
line++;
|
|
}
|
|
if (*line == '\0')
|
|
{
|
|
char *msg =
|
|
xasprintf (_("header field '%s' missing in header\n"),
|
|
field);
|
|
po_xerror (severity, mp, NULL, 0, 0, true, msg);
|
|
free (msg);
|
|
if (severity == PO_SEVERITY_ERROR)
|
|
seen_errors++;
|
|
}
|
|
}
|
|
return seen_errors;
|
|
}
|
|
|
|
|
|
/* Perform all checks on a non-obsolete message.
|
|
Return the number of errors that were seen. */
|
|
int
|
|
check_message (const message_ty *mp,
|
|
const lex_pos_ty *msgid_pos,
|
|
int check_newlines,
|
|
int check_format_strings,
|
|
const struct plural_distribution *distribution,
|
|
int check_header,
|
|
int check_compatibility,
|
|
int check_accelerators, char accelerator_char)
|
|
{
|
|
int seen_errors = 0;
|
|
|
|
if (check_header && is_header (mp))
|
|
seen_errors += check_header_entry (mp, mp->msgstr);
|
|
|
|
seen_errors += check_pair (mp,
|
|
mp->msgid, msgid_pos, mp->msgid_plural,
|
|
mp->msgstr, mp->msgstr_len,
|
|
mp->is_format,
|
|
check_newlines,
|
|
check_format_strings,
|
|
distribution,
|
|
check_compatibility,
|
|
check_accelerators, accelerator_char);
|
|
return seen_errors;
|
|
}
|
|
|
|
|
|
/* Perform all checks on a message list.
|
|
Return the number of errors that were seen. */
|
|
int
|
|
check_message_list (message_list_ty *mlp,
|
|
int ignore_untranslated_messages,
|
|
int ignore_fuzzy_messages,
|
|
int check_newlines,
|
|
int check_format_strings,
|
|
int check_header,
|
|
int check_compatibility,
|
|
int check_accelerators, char accelerator_char)
|
|
{
|
|
int seen_errors = 0;
|
|
struct plural_distribution distribution;
|
|
size_t j;
|
|
|
|
distribution.expr = NULL;
|
|
distribution.often = NULL;
|
|
distribution.often_length = 0;
|
|
distribution.histogram = NULL;
|
|
|
|
if (check_header)
|
|
seen_errors += check_plural (mlp, ignore_untranslated_messages,
|
|
ignore_fuzzy_messages, &distribution);
|
|
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
{
|
|
message_ty *mp = mlp->item[j];
|
|
|
|
if (!mp->obsolete
|
|
&& !(ignore_untranslated_messages && mp->msgstr[0] == '\0')
|
|
&& !(ignore_fuzzy_messages && (mp->is_fuzzy && !is_header (mp))))
|
|
seen_errors += check_message (mp, &mp->pos,
|
|
check_newlines,
|
|
check_format_strings,
|
|
&distribution,
|
|
check_header, check_compatibility,
|
|
check_accelerators, accelerator_char);
|
|
}
|
|
|
|
return seen_errors;
|
|
}
|
|
|
|
|
|
static int
|
|
syntax_check_ellipsis_unicode (const message_ty *mp, const char *msgid)
|
|
{
|
|
const char *str = msgid;
|
|
const char *str_limit = str + strlen (msgid);
|
|
int seen_errors = 0;
|
|
|
|
while (str < str_limit)
|
|
{
|
|
const char *end, *cp;
|
|
ucs4_t ending_char;
|
|
|
|
end = sentence_end (str, &ending_char);
|
|
|
|
/* sentence_end doesn't treat '...' specially. */
|
|
cp = end - (ending_char == '.' ? 2 : 3);
|
|
if (cp >= str && memcmp (cp, "...", 3) == 0)
|
|
{
|
|
po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
|
|
_("ASCII ellipsis ('...') instead of Unicode"));
|
|
seen_errors++;
|
|
}
|
|
|
|
str = end + 1;
|
|
}
|
|
|
|
return seen_errors;
|
|
}
|
|
|
|
|
|
static int
|
|
syntax_check_space_ellipsis (const message_ty *mp, const char *msgid)
|
|
{
|
|
const char *str = msgid;
|
|
const char *str_limit = str + strlen (msgid);
|
|
int seen_errors = 0;
|
|
|
|
while (str < str_limit)
|
|
{
|
|
const char *end, *ellipsis = NULL;
|
|
ucs4_t ending_char;
|
|
|
|
end = sentence_end (str, &ending_char);
|
|
|
|
if (ending_char == 0x2026)
|
|
ellipsis = end;
|
|
else if (ending_char == '.')
|
|
{
|
|
/* sentence_end doesn't treat '...' specially. */
|
|
const char *cp = end - 2;
|
|
if (cp >= str && memcmp (cp, "...", 3) == 0)
|
|
ellipsis = cp;
|
|
}
|
|
else
|
|
{
|
|
/* Look for a '...'. */
|
|
const char *cp = end - 3;
|
|
if (cp >= str && memcmp (cp, "...", 3) == 0)
|
|
ellipsis = cp;
|
|
else
|
|
{
|
|
ucs4_t uc = 0xfffd;
|
|
|
|
/* Look for a U+2026. */
|
|
for (cp = end - 1; cp >= str; cp--)
|
|
{
|
|
u8_mbtouc (&uc, (const unsigned char *) cp, end - cp);
|
|
if (uc != 0xfffd)
|
|
break;
|
|
}
|
|
|
|
if (uc == 0x2026)
|
|
ellipsis = cp;
|
|
}
|
|
}
|
|
|
|
if (ellipsis)
|
|
{
|
|
const char *cp;
|
|
ucs4_t uc = 0xfffd;
|
|
|
|
/* Look at the character before ellipsis. */
|
|
for (cp = ellipsis - 1; cp >= str; cp--)
|
|
{
|
|
u8_mbtouc (&uc, (const unsigned char *) cp, ellipsis - cp);
|
|
if (uc != 0xfffd)
|
|
break;
|
|
}
|
|
|
|
if (uc != 0xfffd && uc_is_space (uc))
|
|
{
|
|
po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
|
|
_("space before ellipsis found in user visible strings"));
|
|
seen_errors++;
|
|
}
|
|
}
|
|
|
|
str = end + 1;
|
|
}
|
|
|
|
return seen_errors;
|
|
}
|
|
|
|
|
|
struct callback_arg
|
|
{
|
|
const message_ty *mp;
|
|
int seen_errors;
|
|
};
|
|
|
|
static void
|
|
syntax_check_quote_unicode_callback (char quote, const char *quoted,
|
|
size_t quoted_length, void *data)
|
|
{
|
|
struct callback_arg *arg = data;
|
|
|
|
switch (quote)
|
|
{
|
|
case '"':
|
|
po_xerror (PO_SEVERITY_ERROR, arg->mp, NULL, 0, 0, false,
|
|
_("ASCII double quote used instead of Unicode"));
|
|
arg->seen_errors++;
|
|
break;
|
|
|
|
case '\'':
|
|
po_xerror (PO_SEVERITY_ERROR, arg->mp, NULL, 0, 0, false,
|
|
_("ASCII single quote used instead of Unicode"));
|
|
arg->seen_errors++;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
static int
|
|
syntax_check_quote_unicode (const message_ty *mp, const char *msgid)
|
|
{
|
|
struct callback_arg arg;
|
|
|
|
arg.mp = mp;
|
|
arg.seen_errors = 0;
|
|
|
|
scan_quoted (msgid, strlen (msgid),
|
|
syntax_check_quote_unicode_callback, &arg);
|
|
|
|
return arg.seen_errors;
|
|
}
|
|
|
|
struct bullet_ty
|
|
{
|
|
int c;
|
|
size_t depth;
|
|
};
|
|
|
|
struct bullet_stack_ty
|
|
{
|
|
struct bullet_ty *items;
|
|
size_t nitems;
|
|
size_t nitems_max;
|
|
};
|
|
|
|
static struct bullet_stack_ty bullet_stack;
|
|
|
|
static int
|
|
syntax_check_bullet_unicode (const message_ty *mp, const char *msgid)
|
|
{
|
|
const char *str = msgid;
|
|
const char *str_limit = str + strlen (msgid);
|
|
struct bullet_ty *last_bullet = NULL;
|
|
bool seen_error = false;
|
|
|
|
bullet_stack.nitems = 0;
|
|
|
|
while (str < str_limit)
|
|
{
|
|
const char *p = str, *end;
|
|
|
|
while (p < str_limit && c_isspace (*p))
|
|
p++;
|
|
|
|
if ((*p == '*' || *p == '-') && *(p + 1) == ' ')
|
|
{
|
|
size_t depth = p - str;
|
|
if (last_bullet == NULL || depth > last_bullet->depth)
|
|
{
|
|
struct bullet_ty bullet;
|
|
|
|
bullet.c = *p;
|
|
bullet.depth = depth;
|
|
|
|
if (bullet_stack.nitems >= bullet_stack.nitems_max)
|
|
{
|
|
bullet_stack.nitems_max = 2 * bullet_stack.nitems_max + 4;
|
|
bullet_stack.items = xrealloc (bullet_stack.items,
|
|
bullet_stack.nitems_max
|
|
* sizeof (struct bullet_ty));
|
|
}
|
|
|
|
last_bullet = &bullet_stack.items[bullet_stack.nitems++];
|
|
memcpy (last_bullet, &bullet, sizeof (struct bullet_ty));
|
|
}
|
|
else
|
|
{
|
|
if (depth < last_bullet->depth)
|
|
{
|
|
if (bullet_stack.nitems > 1)
|
|
{
|
|
bullet_stack.nitems--;
|
|
last_bullet =
|
|
&bullet_stack.items[bullet_stack.nitems - 1];
|
|
}
|
|
else
|
|
last_bullet = NULL;
|
|
}
|
|
|
|
if (last_bullet && depth == last_bullet->depth)
|
|
{
|
|
if (last_bullet->c != *p)
|
|
last_bullet->c = *p;
|
|
else
|
|
{
|
|
seen_error = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
bullet_stack.nitems = 0;
|
|
last_bullet = NULL;
|
|
}
|
|
|
|
end = strchrnul (str, '\n');
|
|
str = end + 1;
|
|
}
|
|
|
|
if (seen_error)
|
|
{
|
|
char *msg;
|
|
msg = xasprintf (_("ASCII bullet ('%c') instead of Unicode"),
|
|
last_bullet->c);
|
|
po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false, msg);
|
|
free (msg);
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
typedef int (* syntax_check_function) (const message_ty *mp, const char *msgid);
|
|
static const syntax_check_function sc_funcs[NSYNTAXCHECKS] =
|
|
{
|
|
syntax_check_ellipsis_unicode,
|
|
syntax_check_space_ellipsis,
|
|
syntax_check_quote_unicode,
|
|
syntax_check_bullet_unicode
|
|
};
|
|
|
|
/* Perform all syntax checks on a non-obsolete message.
|
|
Return the number of errors that were seen. */
|
|
static int
|
|
syntax_check_message (const message_ty *mp)
|
|
{
|
|
int seen_errors = 0;
|
|
int i;
|
|
|
|
for (i = 0; i < NSYNTAXCHECKS; i++)
|
|
{
|
|
if (mp->do_syntax_check[i] == yes)
|
|
{
|
|
seen_errors += sc_funcs[i] (mp, mp->msgid);
|
|
if (mp->msgid_plural)
|
|
seen_errors += sc_funcs[i] (mp, mp->msgid_plural);
|
|
}
|
|
}
|
|
|
|
return seen_errors;
|
|
}
|
|
|
|
|
|
/* Perform all syntax checks on a message list.
|
|
Return the number of errors that were seen. */
|
|
int
|
|
syntax_check_message_list (message_list_ty *mlp)
|
|
{
|
|
int seen_errors = 0;
|
|
size_t j;
|
|
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
{
|
|
message_ty *mp = mlp->item[j];
|
|
|
|
if (!is_header (mp))
|
|
seen_errors += syntax_check_message (mp);
|
|
}
|
|
|
|
return seen_errors;
|
|
}
|