Add -lm dependency for gettextlib to fix LTO build
[platform/upstream/gettext.git] / gettext-tools / src / msgl-check.c
1 /* Checking of messages in PO files.
2    Copyright (C) 1995-1998, 2000-2008, 2010-2015 Free Software
3    Foundation, Inc.
4    Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
5
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 /* Specification.  */
24 #include "msgl-check.h"
25
26 #include <limits.h>
27 #include <setjmp.h>
28 #include <signal.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <stdarg.h>
33
34 #include "c-ctype.h"
35 #include "xalloc.h"
36 #include "xvasprintf.h"
37 #include "po-xerror.h"
38 #include "format.h"
39 #include "plural-exp.h"
40 #include "plural-eval.h"
41 #include "plural-table.h"
42 #include "c-strstr.h"
43 #include "message.h"
44 #include "quote.h"
45 #include "sentence.h"
46 #include "unictype.h"
47 #include "unistr.h"
48 #include "gettext.h"
49
50 #define _(str) gettext (str)
51
52 #define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
53
54
55 /* Evaluates the plural formula for min <= n <= max
56    and returns the estimated number of times the value j was assumed.  */
57 static unsigned int
58 plural_expression_histogram (const struct plural_distribution *self,
59                              int min, int max, unsigned long j)
60 {
61   if (min < 0)
62     min = 0;
63   /* Limit the number of evaluations.  Nothing interesting happens beyond
64      1000.  */
65   if (max - min > 1000)
66     max = min + 1000;
67   if (min <= max)
68     {
69       const struct expression *expr = self->expr;
70       unsigned long n;
71       unsigned int count;
72
73       /* Protect against arithmetic exceptions.  */
74       install_sigfpe_handler ();
75
76       count = 0;
77       for (n = min; n <= max; n++)
78         {
79           unsigned long val = plural_eval (expr, n);
80
81           if (val == j)
82             count++;
83         }
84
85       /* End of protection against arithmetic exceptions.  */
86       uninstall_sigfpe_handler ();
87
88       return count;
89     }
90   else
91     return 0;
92 }
93
94
95 /* Check the values returned by plural_eval.
96    Signals the errors through po_xerror.
97    Return the number of errors that were seen.
98    If no errors, returns in *DISTRIBUTION information about the plural_eval
99    values distribution.  */
100 int
101 check_plural_eval (const struct expression *plural_expr,
102                    unsigned long nplurals_value,
103                    const message_ty *header,
104                    struct plural_distribution *distribution)
105 {
106   /* Do as if the plural formula assumes a value N infinitely often if it
107      assumes it at least 5 times.  */
108 #define OFTEN 5
109   unsigned char * volatile array;
110
111   /* Allocate a distribution array.  */
112   if (nplurals_value <= 100)
113     array = XCALLOC (nplurals_value, unsigned char);
114   else
115     /* nplurals_value is nonsense.  Don't risk an out-of-memory.  */
116     array = NULL;
117
118   if (sigsetjmp (sigfpe_exit, 1) == 0)
119     {
120       unsigned long n;
121
122       /* Protect against arithmetic exceptions.  */
123       install_sigfpe_handler ();
124
125       for (n = 0; n <= 1000; n++)
126         {
127           unsigned long val = plural_eval (plural_expr, n);
128
129           if ((long) val < 0)
130             {
131               /* End of protection against arithmetic exceptions.  */
132               uninstall_sigfpe_handler ();
133
134               po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false,
135                          _("plural expression can produce negative values"));
136               free (array);
137               return 1;
138             }
139           else if (val >= nplurals_value)
140             {
141               char *msg;
142
143               /* End of protection against arithmetic exceptions.  */
144               uninstall_sigfpe_handler ();
145
146               msg = xasprintf (_("nplurals = %lu but plural expression can produce values as large as %lu"),
147                                nplurals_value, val);
148               po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
149               free (msg);
150               free (array);
151               return 1;
152             }
153
154           if (array != NULL && array[val] < OFTEN)
155             array[val]++;
156         }
157
158       /* End of protection against arithmetic exceptions.  */
159       uninstall_sigfpe_handler ();
160
161       /* Normalize the array[val] statistics.  */
162       if (array != NULL)
163         {
164           unsigned long val;
165
166           for (val = 0; val < nplurals_value; val++)
167             array[val] = (array[val] == OFTEN ? 1 : 0);
168         }
169
170       distribution->expr = plural_expr;
171       distribution->often = array;
172       distribution->often_length = (array != NULL ? nplurals_value : 0);
173       distribution->histogram = plural_expression_histogram;
174
175       return 0;
176     }
177   else
178     {
179       /* Caught an arithmetic exception.  */
180       const char *msg;
181
182       /* End of protection against arithmetic exceptions.  */
183       uninstall_sigfpe_handler ();
184
185 #if USE_SIGINFO
186       switch (sigfpe_code)
187 #endif
188         {
189 #if USE_SIGINFO
190 # ifdef FPE_INTDIV
191         case FPE_INTDIV:
192           msg = _("plural expression can produce division by zero");
193           break;
194 # endif
195 # ifdef FPE_INTOVF
196         case FPE_INTOVF:
197           msg = _("plural expression can produce integer overflow");
198           break;
199 # endif
200         default:
201 #endif
202           msg = _("plural expression can produce arithmetic exceptions, possibly division by zero");
203         }
204
205       po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
206
207       free (array);
208
209       return 1;
210     }
211 #undef OFTEN
212 }
213
214
215 /* Try to help the translator by looking up the right plural formula for her.
216    Return a freshly allocated multiline help string, or NULL.  */
217 static char *
218 plural_help (const char *nullentry)
219 {
220   struct plural_table_entry *ptentry = NULL;
221
222   {
223     const char *language;
224
225     language = c_strstr (nullentry, "Language: ");
226     if (language != NULL)
227       {
228         size_t len;
229
230         language += 10;
231         len = strcspn (language, " \t\n");
232         if (len > 0)
233           {
234             size_t j;
235
236             for (j = 0; j < plural_table_size; j++)
237               if (len == strlen (plural_table[j].lang)
238                   && strncmp (language, plural_table[j].lang, len) == 0)
239                 {
240                   ptentry = &plural_table[j];
241                   break;
242                 }
243           }
244       }
245   }
246
247   if (ptentry == NULL)
248     {
249       const char *language;
250
251       language = c_strstr (nullentry, "Language-Team: ");
252       if (language != NULL)
253         {
254           size_t j;
255
256           language += 15;
257           for (j = 0; j < plural_table_size; j++)
258             if (strncmp (language,
259                          plural_table[j].language,
260                          strlen (plural_table[j].language)) == 0)
261               {
262                 ptentry = &plural_table[j];
263                 break;
264               }
265         }
266     }
267
268   if (ptentry != NULL)
269     {
270       char *helpline1 =
271         xasprintf (_("Try using the following, valid for %s:"),
272                    ptentry->language);
273       char *help =
274         xasprintf ("%s\n\"Plural-Forms: %s\\n\"\n",
275                    helpline1, ptentry->value);
276       free (helpline1);
277       return help;
278     }
279   return NULL;
280 }
281
282
283 /* Perform plural expression checking.
284    Return the number of errors that were seen.
285    If no errors, returns in *DISTRIBUTION information about the plural_eval
286    values distribution.  */
287 static int
288 check_plural (message_list_ty *mlp,
289               int ignore_untranslated_messages,
290               int ignore_fuzzy_messages,
291               struct plural_distribution *distributionp)
292 {
293   int seen_errors = 0;
294   const message_ty *has_plural;
295   unsigned long min_nplurals;
296   const message_ty *min_pos;
297   unsigned long max_nplurals;
298   const message_ty *max_pos;
299   struct plural_distribution distribution;
300   size_t j;
301   message_ty *header;
302
303   /* Determine whether mlp has plural entries.  */
304   has_plural = NULL;
305   min_nplurals = ULONG_MAX;
306   min_pos = NULL;
307   max_nplurals = 0;
308   max_pos = NULL;
309   distribution.expr = NULL;
310   distribution.often = NULL;
311   distribution.often_length = 0;
312   distribution.histogram = NULL;
313   for (j = 0; j < mlp->nitems; j++)
314     {
315       message_ty *mp = mlp->item[j];
316
317       if (!mp->obsolete
318           && !(ignore_untranslated_messages && mp->msgstr[0] == '\0')
319           && !(ignore_fuzzy_messages && (mp->is_fuzzy && !is_header (mp)))
320           && mp->msgid_plural != NULL)
321         {
322           const char *p;
323           const char *p_end;
324           unsigned long n;
325
326           if (has_plural == NULL)
327             has_plural = mp;
328
329           n = 0;
330           for (p = mp->msgstr, p_end = p + mp->msgstr_len;
331                p < p_end;
332                p += strlen (p) + 1)
333             n++;
334           if (min_nplurals > n)
335             {
336               min_nplurals = n;
337               min_pos = mp;
338             }
339           if (max_nplurals < n)
340             {
341               max_nplurals = n;
342               max_pos = mp;
343             }
344         }
345     }
346
347   /* Look at the plural entry for this domain.
348      Cf, function extract_plural_expression.  */
349   header = message_list_search (mlp, NULL, "");
350   if (header != NULL && !header->obsolete)
351     {
352       const char *nullentry;
353       const char *plural;
354       const char *nplurals;
355
356       nullentry = header->msgstr;
357
358       plural = c_strstr (nullentry, "plural=");
359       nplurals = c_strstr (nullentry, "nplurals=");
360       if (plural == NULL && has_plural != NULL)
361         {
362           const char *msg1 =
363             _("message catalog has plural form translations");
364           const char *msg2 =
365             _("but header entry lacks a \"plural=EXPRESSION\" attribute");
366           char *help = plural_help (nullentry);
367
368           if (help != NULL)
369             {
370               char *msg2ext = xasprintf ("%s\n%s", msg2, help);
371               po_xerror2 (PO_SEVERITY_ERROR,
372                           has_plural, NULL, 0, 0, false, msg1,
373                           header, NULL, 0, 0, true, msg2ext);
374               free (msg2ext);
375               free (help);
376             }
377           else
378             po_xerror2 (PO_SEVERITY_ERROR,
379                         has_plural, NULL, 0, 0, false, msg1,
380                         header, NULL, 0, 0, false, msg2);
381
382           seen_errors++;
383         }
384       if (nplurals == NULL && has_plural != NULL)
385         {
386           const char *msg1 =
387             _("message catalog has plural form translations");
388           const char *msg2 =
389             _("but header entry lacks a \"nplurals=INTEGER\" attribute");
390           char *help = plural_help (nullentry);
391
392           if (help != NULL)
393             {
394               char *msg2ext = xasprintf ("%s\n%s", msg2, help);
395               po_xerror2 (PO_SEVERITY_ERROR,
396                           has_plural, NULL, 0, 0, false, msg1,
397                           header, NULL, 0, 0, true, msg2ext);
398               free (msg2ext);
399               free (help);
400             }
401           else
402             po_xerror2 (PO_SEVERITY_ERROR,
403                         has_plural, NULL, 0, 0, false, msg1,
404                         header, NULL, 0, 0, false, msg2);
405
406           seen_errors++;
407         }
408       if (plural != NULL && nplurals != NULL)
409         {
410           const char *endp;
411           unsigned long int nplurals_value;
412           struct parse_args args;
413           const struct expression *plural_expr;
414
415           /* First check the number.  */
416           nplurals += 9;
417           while (*nplurals != '\0' && c_isspace ((unsigned char) *nplurals))
418             ++nplurals;
419           endp = nplurals;
420           nplurals_value = 0;
421           if (*nplurals >= '0' && *nplurals <= '9')
422             nplurals_value = strtoul (nplurals, (char **) &endp, 10);
423           if (nplurals == endp)
424             {
425               const char *msg = _("invalid nplurals value");
426               char *help = plural_help (nullentry);
427
428               if (help != NULL)
429                 {
430                   char *msgext = xasprintf ("%s\n%s", msg, help);
431                   po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, true,
432                              msgext);
433                   free (msgext);
434                   free (help);
435                 }
436               else
437                 po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
438
439               seen_errors++;
440             }
441
442           /* Then check the expression.  */
443           plural += 7;
444           args.cp = plural;
445           if (parse_plural_expression (&args) != 0)
446             {
447               const char *msg = _("invalid plural expression");
448               char *help = plural_help (nullentry);
449
450               if (help != NULL)
451                 {
452                   char *msgext = xasprintf ("%s\n%s", msg, help);
453                   po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, true,
454                              msgext);
455                   free (msgext);
456                   free (help);
457                 }
458               else
459                 po_xerror (PO_SEVERITY_ERROR, header, NULL, 0, 0, false, msg);
460
461               seen_errors++;
462             }
463           plural_expr = args.res;
464
465           /* See whether nplurals and plural fit together.  */
466           if (!seen_errors)
467             seen_errors =
468               check_plural_eval (plural_expr, nplurals_value, header,
469                                  &distribution);
470
471           /* Check the number of plurals of the translations.  */
472           if (!seen_errors)
473             {
474               if (min_nplurals < nplurals_value)
475                 {
476                   char *msg1 =
477                     xasprintf (_("nplurals = %lu"), nplurals_value);
478                   char *msg2 =
479                     xasprintf (ngettext ("but some messages have only one plural form",
480                                          "but some messages have only %lu plural forms",
481                                          min_nplurals),
482                                min_nplurals);
483                   po_xerror2 (PO_SEVERITY_ERROR,
484                               header, NULL, 0, 0, false, msg1,
485                               min_pos, NULL, 0, 0, false, msg2);
486                   free (msg2);
487                   free (msg1);
488                   seen_errors++;
489                 }
490               else if (max_nplurals > nplurals_value)
491                 {
492                   char *msg1 =
493                     xasprintf (_("nplurals = %lu"), nplurals_value);
494                   char *msg2 =
495                     xasprintf (ngettext ("but some messages have one plural form",
496                                          "but some messages have %lu plural forms",
497                                          max_nplurals),
498                                max_nplurals);
499                   po_xerror2 (PO_SEVERITY_ERROR,
500                               header, NULL, 0, 0, false, msg1,
501                               max_pos, NULL, 0, 0, false, msg2);
502                   free (msg2);
503                   free (msg1);
504                   seen_errors++;
505                 }
506               /* The only valid case is max_nplurals <= n <= min_nplurals,
507                  which means either has_plural == NULL or
508                  max_nplurals = n = min_nplurals.  */
509             }
510         }
511       else
512         goto no_plural;
513     }
514   else
515     {
516       if (has_plural != NULL)
517         {
518           po_xerror (PO_SEVERITY_ERROR, has_plural, NULL, 0, 0, false,
519                      _("message catalog has plural form translations, but lacks a header entry with \"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\""));
520           seen_errors++;
521         }
522      no_plural:
523       /* By default, the Germanic formula (n != 1) is used.  */
524       distribution.expr = &germanic_plural;
525       {
526         unsigned char *array = XCALLOC (2, unsigned char);
527         array[1] = 1;
528         distribution.often = array;
529       }
530       distribution.often_length = 2;
531       distribution.histogram = plural_expression_histogram;
532     }
533
534   /* distribution is not needed if we report errors.
535      Also, if there was an error due to  max_nplurals > nplurals_value,
536      we must not use distribution because we would be doing out-of-bounds
537      array accesses.  */
538   if (seen_errors > 0)
539     free ((unsigned char *) distribution.often);
540   else
541     *distributionp = distribution;
542
543   return seen_errors;
544 }
545
546
547 /* Signal an error when checking format strings.  */
548 static const message_ty *curr_mp;
549 static lex_pos_ty curr_msgid_pos;
550 static void
551 formatstring_error_logger (const char *format, ...)
552 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 7) || __GNUC__ > 2)
553      __attribute__ ((__format__ (__printf__, 1, 2)))
554 #endif
555 ;
556 static void
557 formatstring_error_logger (const char *format, ...)
558 {
559   va_list args;
560   char *msg;
561
562   va_start (args, format);
563   if (vasprintf (&msg, format, args) < 0)
564     error (EXIT_FAILURE, 0, _("memory exhausted"));
565   va_end (args);
566   po_xerror (PO_SEVERITY_ERROR,
567              curr_mp, curr_msgid_pos.file_name, curr_msgid_pos.line_number,
568              (size_t)(-1), false, msg);
569   free (msg);
570 }
571
572
573 /* Perform miscellaneous checks on a message.
574    PLURAL_DISTRIBUTION is either NULL or an array of nplurals elements,
575    PLURAL_DISTRIBUTION[j] being true if the value j appears to be assumed
576    infinitely often by the plural formula.
577    PLURAL_DISTRIBUTION_LENGTH is the length of the PLURAL_DISTRIBUTION
578    array.  */
579 static int
580 check_pair (const message_ty *mp,
581             const char *msgid,
582             const lex_pos_ty *msgid_pos,
583             const char *msgid_plural,
584             const char *msgstr, size_t msgstr_len,
585             const enum is_format is_format[NFORMATS],
586             int check_newlines,
587             int check_format_strings,
588             const struct plural_distribution *distribution,
589             int check_compatibility,
590             int check_accelerators, char accelerator_char)
591 {
592   int seen_errors;
593   int has_newline;
594   unsigned int j;
595
596   /* If the msgid string is empty we have the special entry reserved for
597      information about the translation.  */
598   if (msgid[0] == '\0')
599     return 0;
600
601   seen_errors = 0;
602
603   if (check_newlines)
604     {
605       /* Test 1: check whether all or none of the strings begin with a '\n'.  */
606       has_newline = (msgid[0] == '\n');
607 #define TEST_NEWLINE(p) (p[0] == '\n')
608       if (msgid_plural != NULL)
609         {
610           const char *p;
611
612           if (TEST_NEWLINE(msgid_plural) != has_newline)
613             {
614               po_xerror (PO_SEVERITY_ERROR,
615                          mp, msgid_pos->file_name, msgid_pos->line_number,
616                          (size_t)(-1), false, _("\
617 'msgid' and 'msgid_plural' entries do not both begin with '\\n'"));
618               seen_errors++;
619             }
620           for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++)
621             if (TEST_NEWLINE(p) != has_newline)
622               {
623                 char *msg =
624                   xasprintf (_("\
625 'msgid' and 'msgstr[%u]' entries do not both begin with '\\n'"), j);
626                 po_xerror (PO_SEVERITY_ERROR,
627                            mp, msgid_pos->file_name, msgid_pos->line_number,
628                            (size_t)(-1), false, msg);
629                 free (msg);
630                 seen_errors++;
631               }
632         }
633       else
634         {
635           if (TEST_NEWLINE(msgstr) != has_newline)
636             {
637               po_xerror (PO_SEVERITY_ERROR,
638                          mp, msgid_pos->file_name, msgid_pos->line_number,
639                          (size_t)(-1), false, _("\
640 'msgid' and 'msgstr' entries do not both begin with '\\n'"));
641               seen_errors++;
642             }
643         }
644 #undef TEST_NEWLINE
645
646       /* Test 2: check whether all or none of the strings end with a '\n'.  */
647       has_newline = (msgid[strlen (msgid) - 1] == '\n');
648 #define TEST_NEWLINE(p) (p[0] != '\0' && p[strlen (p) - 1] == '\n')
649       if (msgid_plural != NULL)
650         {
651           const char *p;
652
653           if (TEST_NEWLINE(msgid_plural) != has_newline)
654             {
655               po_xerror (PO_SEVERITY_ERROR,
656                          mp, msgid_pos->file_name, msgid_pos->line_number,
657                          (size_t)(-1), false, _("\
658 'msgid' and 'msgid_plural' entries do not both end with '\\n'"));
659               seen_errors++;
660             }
661           for (p = msgstr, j = 0; p < msgstr + msgstr_len; p += strlen (p) + 1, j++)
662             if (TEST_NEWLINE(p) != has_newline)
663               {
664                 char *msg =
665                   xasprintf (_("\
666 'msgid' and 'msgstr[%u]' entries do not both end with '\\n'"), j);
667                 po_xerror (PO_SEVERITY_ERROR,
668                            mp, msgid_pos->file_name, msgid_pos->line_number,
669                            (size_t)(-1), false, msg);
670                 free (msg);
671                 seen_errors++;
672               }
673         }
674       else
675         {
676           if (TEST_NEWLINE(msgstr) != has_newline)
677             {
678               po_xerror (PO_SEVERITY_ERROR,
679                          mp, msgid_pos->file_name, msgid_pos->line_number,
680                          (size_t)(-1), false, _("\
681 'msgid' and 'msgstr' entries do not both end with '\\n'"));
682               seen_errors++;
683             }
684         }
685 #undef TEST_NEWLINE
686     }
687
688   if (check_compatibility && msgid_plural != NULL)
689     {
690       po_xerror (PO_SEVERITY_ERROR,
691                  mp, msgid_pos->file_name, msgid_pos->line_number,
692                  (size_t)(-1), false, _("\
693 plural handling is a GNU gettext extension"));
694       seen_errors++;
695     }
696
697   if (check_format_strings)
698     /* Test 3: Check whether both formats strings contain the same number
699        of format specifications.  */
700     {
701       curr_mp = mp;
702       curr_msgid_pos = *msgid_pos;
703       seen_errors +=
704         check_msgid_msgstr_format (msgid, msgid_plural, msgstr, msgstr_len,
705                                    is_format, mp->range, distribution,
706                                    formatstring_error_logger);
707     }
708
709   if (check_accelerators && msgid_plural == NULL)
710     /* Test 4: Check that if msgid is a menu item with a keyboard accelerator,
711        the msgstr has an accelerator as well.  A keyboard accelerator is
712        designated by an immediately preceding '&'.  We cannot check whether
713        two accelerators collide, only whether the translator has bothered
714        thinking about them.  */
715     {
716       const char *p;
717
718       /* We are only interested in msgids that contain exactly one '&'.  */
719       p = strchr (msgid, accelerator_char);
720       if (p != NULL && strchr (p + 1, accelerator_char) == NULL)
721         {
722           /* Count the number of '&' in msgstr, but ignore '&&'.  */
723           unsigned int count = 0;
724
725           for (p = msgstr; (p = strchr (p, accelerator_char)) != NULL; p++)
726             if (p[1] == accelerator_char)
727               p++;
728             else
729               count++;
730
731           if (count == 0)
732             {
733               char *msg =
734                 xasprintf (_("msgstr lacks the keyboard accelerator mark '%c'"),
735                            accelerator_char);
736               po_xerror (PO_SEVERITY_ERROR,
737                          mp, msgid_pos->file_name, msgid_pos->line_number,
738                          (size_t)(-1), false, msg);
739               free (msg);
740               seen_errors++;
741             }
742           else if (count > 1)
743             {
744               char *msg =
745                 xasprintf (_("msgstr has too many keyboard accelerator marks '%c'"),
746                            accelerator_char);
747               po_xerror (PO_SEVERITY_ERROR,
748                          mp, msgid_pos->file_name, msgid_pos->line_number,
749                          (size_t)(-1), false, msg);
750               free (msg);
751               seen_errors++;
752             }
753         }
754     }
755
756   return seen_errors;
757 }
758
759
760 /* Perform miscellaneous checks on a header entry.  */
761 static int
762 check_header_entry (const message_ty *mp, const char *msgstr_string)
763 {
764   static const char *required_fields[] =
765   {
766     "Project-Id-Version", "PO-Revision-Date", "Last-Translator",
767     "Language-Team", "MIME-Version", "Content-Type",
768     "Content-Transfer-Encoding",
769     /* These are recommended but not yet required.  */
770     "Language"
771   };
772   static const char *default_values[] =
773   {
774     "PACKAGE VERSION", "YEAR-MO-DA HO:MI+ZONE", "FULL NAME <EMAIL@ADDRESS>", "LANGUAGE <LL@li.org>", NULL,
775     "text/plain; charset=CHARSET", "ENCODING",
776     ""
777   };
778   const size_t nfields = SIZEOF (required_fields);
779   /* FIXME: We could check if a required header field is missing and
780      report it as error.  However, it's could be too rigorous and
781      break backward compatibility.  */
782 #if 0
783   const size_t nrequiredfields = nfields - 1;
784 #endif
785   int seen_errors = 0;
786   int cnt;
787
788   for (cnt = 0; cnt < nfields; ++cnt)
789     {
790 #if 0
791       int severity =
792         (cnt < nrequiredfields ? PO_SEVERITY_ERROR : PO_SEVERITY_WARNING);
793 #else
794       int severity =
795         PO_SEVERITY_WARNING;
796 #endif
797       const char *field = required_fields[cnt];
798       size_t len = strlen (field);
799       const char *line;
800
801       for (line = msgstr_string; *line != '\0'; )
802         {
803           if (strncmp (line, field, len) == 0 && line[len] == ':')
804             {
805               const char *p = line + len + 1;
806
807               /* Test whether the field's value, starting at p, is the default
808                  value.  */
809               if (*p == ' ')
810                 p++;
811               if (default_values[cnt] != NULL
812                   && strncmp (p, default_values[cnt],
813                               strlen (default_values[cnt])) == 0)
814                 {
815                   p += strlen (default_values[cnt]);
816                   if (*p == '\0' || *p == '\n')
817                     {
818                       char *msg =
819                         xasprintf (_("header field '%s' still has the initial default value\n"),
820                                    field);
821                       po_xerror (severity, mp, NULL, 0, 0, true, msg);
822                       free (msg);
823                       if (severity == PO_SEVERITY_ERROR)
824                         seen_errors++;
825                     }
826                 }
827               break;
828             }
829           line = strchrnul (line, '\n');
830           if (*line == '\n')
831             line++;
832         }
833       if (*line == '\0')
834         {
835           char *msg =
836             xasprintf (_("header field '%s' missing in header\n"),
837                        field);
838           po_xerror (severity, mp, NULL, 0, 0, true, msg);
839           free (msg);
840           if (severity == PO_SEVERITY_ERROR)
841             seen_errors++;
842         }
843     }
844   return seen_errors;
845 }
846
847
848 /* Perform all checks on a non-obsolete message.
849    Return the number of errors that were seen.  */
850 int
851 check_message (const message_ty *mp,
852                const lex_pos_ty *msgid_pos,
853                int check_newlines,
854                int check_format_strings,
855                const struct plural_distribution *distribution,
856                int check_header,
857                int check_compatibility,
858                int check_accelerators, char accelerator_char)
859 {
860   int seen_errors = 0;
861
862   if (check_header && is_header (mp))
863     seen_errors += check_header_entry (mp, mp->msgstr);
864
865   seen_errors += check_pair (mp,
866                              mp->msgid, msgid_pos, mp->msgid_plural,
867                              mp->msgstr, mp->msgstr_len,
868                              mp->is_format,
869                              check_newlines,
870                              check_format_strings,
871                              distribution,
872                              check_compatibility,
873                              check_accelerators, accelerator_char);
874   return seen_errors;
875 }
876
877
878 /* Perform all checks on a message list.
879    Return the number of errors that were seen.  */
880 int
881 check_message_list (message_list_ty *mlp,
882                     int ignore_untranslated_messages,
883                     int ignore_fuzzy_messages,
884                     int check_newlines,
885                     int check_format_strings,
886                     int check_header,
887                     int check_compatibility,
888                     int check_accelerators, char accelerator_char)
889 {
890   int seen_errors = 0;
891   struct plural_distribution distribution;
892   size_t j;
893
894   distribution.expr = NULL;
895   distribution.often = NULL;
896   distribution.often_length = 0;
897   distribution.histogram = NULL;
898
899   if (check_header)
900     seen_errors += check_plural (mlp, ignore_untranslated_messages,
901                                  ignore_fuzzy_messages, &distribution);
902
903   for (j = 0; j < mlp->nitems; j++)
904     {
905       message_ty *mp = mlp->item[j];
906
907       if (!mp->obsolete
908           && !(ignore_untranslated_messages && mp->msgstr[0] == '\0')
909           && !(ignore_fuzzy_messages && (mp->is_fuzzy && !is_header (mp))))
910         seen_errors += check_message (mp, &mp->pos,
911                                       check_newlines,
912                                       check_format_strings,
913                                       &distribution,
914                                       check_header, check_compatibility,
915                                       check_accelerators, accelerator_char);
916     }
917
918   return seen_errors;
919 }
920
921
922 static int
923 syntax_check_ellipsis_unicode (const message_ty *mp, const char *msgid)
924 {
925   const char *str = msgid;
926   const char *str_limit = str + strlen (msgid);
927   int seen_errors = 0;
928
929   while (str < str_limit)
930     {
931       const char *end, *cp;
932       ucs4_t ending_char;
933
934       end = sentence_end (str, &ending_char);
935
936       /* sentence_end doesn't treat '...' specially.  */
937       cp = end - (ending_char == '.' ? 2 : 3);
938       if (cp >= str && memcmp (cp, "...", 3) == 0)
939         {
940           po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
941                      _("ASCII ellipsis ('...') instead of Unicode"));
942           seen_errors++;
943         }
944
945       str = end + 1;
946     }
947
948   return seen_errors;
949 }
950
951
952 static int
953 syntax_check_space_ellipsis (const message_ty *mp, const char *msgid)
954 {
955   const char *str = msgid;
956   const char *str_limit = str + strlen (msgid);
957   int seen_errors = 0;
958
959   while (str < str_limit)
960     {
961       const char *end, *ellipsis = NULL;
962       ucs4_t ending_char;
963
964       end = sentence_end (str, &ending_char);
965
966       if (ending_char == 0x2026)
967         ellipsis = end;
968       else if (ending_char == '.')
969         {
970           /* sentence_end doesn't treat '...' specially.  */
971           const char *cp = end - 2;
972           if (cp >= str && memcmp (cp, "...", 3) == 0)
973             ellipsis = cp;
974         }
975       else
976         {
977           /* Look for a '...'.  */
978           const char *cp = end - 3;
979           if (cp >= str && memcmp (cp, "...", 3) == 0)
980             ellipsis = cp;
981           else
982             {
983               ucs4_t uc = 0xfffd;
984
985               /* Look for a U+2026.  */
986               for (cp = end - 1; cp >= str; cp--)
987                 {
988                   u8_mbtouc (&uc, (const unsigned char *) cp, ellipsis - cp);
989                   if (uc != 0xfffd)
990                     break;
991                 }
992
993               if (uc == 0x2026)
994                 ellipsis = cp;
995             }
996         }
997
998       if (ellipsis)
999         {
1000           const char *cp;
1001           ucs4_t uc = 0xfffd;
1002
1003           /* Look at the character before ellipsis.  */
1004           for (cp = ellipsis - 1; cp >= str; cp--)
1005             {
1006               u8_mbtouc (&uc, (const unsigned char *) cp, ellipsis - cp);
1007               if (uc != 0xfffd)
1008                 break;
1009             }
1010
1011           if (uc != 0xfffd && uc_is_space (uc))
1012             {
1013               po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
1014                          _("\
1015 space before ellipsis found in user visible strings"));
1016               seen_errors++;
1017             }
1018         }
1019
1020       str = end + 1;
1021     }
1022
1023   return seen_errors;
1024 }
1025
1026
1027 struct callback_arg
1028 {
1029   const message_ty *mp;
1030   int seen_errors;
1031 };
1032
1033 static void
1034 syntax_check_quote_unicode_callback (char quote, const char *quoted,
1035                                      size_t quoted_length, void *data)
1036 {
1037   struct callback_arg *arg = data;
1038
1039   switch (quote)
1040     {
1041     case '"':
1042       po_xerror (PO_SEVERITY_ERROR, arg->mp, NULL, 0, 0, false,
1043                  _("ASCII double quote used instead of Unicode"));
1044       arg->seen_errors++;
1045       break;
1046
1047     case '\'':
1048       po_xerror (PO_SEVERITY_ERROR, arg->mp, NULL, 0, 0, false,
1049                  _("ASCII single quote used instead of Unicode"));
1050       arg->seen_errors++;
1051       break;
1052
1053     default:
1054       break;
1055     }
1056 }
1057
1058 static int
1059 syntax_check_quote_unicode (const message_ty *mp, const char *msgid)
1060 {
1061   struct callback_arg arg;
1062
1063   arg.mp = mp;
1064   arg.seen_errors = 0;
1065
1066   scan_quoted (msgid, strlen (msgid),
1067                syntax_check_quote_unicode_callback, &arg);
1068
1069   return arg.seen_errors;
1070 }
1071
1072
1073 typedef int (* syntax_check_function) (const message_ty *mp, const char *msgid);
1074 static const syntax_check_function sc_funcs[NSYNTAXCHECKS] =
1075 {
1076   syntax_check_ellipsis_unicode,
1077   syntax_check_space_ellipsis,
1078   syntax_check_quote_unicode
1079 };
1080
1081 /* Perform all syntax checks on a non-obsolete message.
1082    Return the number of errors that were seen.  */
1083 static int
1084 syntax_check_message (const message_ty *mp)
1085 {
1086   int seen_errors = 0;
1087   int i;
1088
1089   for (i = 0; i < NSYNTAXCHECKS; i++)
1090     {
1091       if (mp->do_syntax_check[i] == yes)
1092         {
1093           seen_errors += sc_funcs[i] (mp, mp->msgid);
1094           if (mp->msgid_plural)
1095             seen_errors += sc_funcs[i] (mp, mp->msgid_plural);
1096         }
1097     }
1098
1099   return seen_errors;
1100 }
1101
1102
1103 /* Perform all syntax checks on a message list.
1104    Return the number of errors that were seen.  */
1105 int
1106 syntax_check_message_list (message_list_ty *mlp)
1107 {
1108   int seen_errors = 0;
1109   size_t j;
1110
1111   for (j = 0; j < mlp->nitems; j++)
1112     {
1113       message_ty *mp = mlp->item[j];
1114
1115       if (!is_header (mp))
1116         seen_errors += syntax_check_message (mp);
1117     }
1118
1119   return seen_errors;
1120 }