xref: /netbsd-src/external/gpl2/gettext/dist/gettext-tools/src/format.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
1*946379e7Schristos /* Format strings.
2*946379e7Schristos    Copyright (C) 2001-2006 Free Software Foundation, Inc.
3*946379e7Schristos    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
4*946379e7Schristos 
5*946379e7Schristos    This program is free software; you can redistribute it and/or modify
6*946379e7Schristos    it under the terms of the GNU General Public License as published by
7*946379e7Schristos    the Free Software Foundation; either version 2, or (at your option)
8*946379e7Schristos    any later version.
9*946379e7Schristos 
10*946379e7Schristos    This program is distributed in the hope that it will be useful,
11*946379e7Schristos    but WITHOUT ANY WARRANTY; without even the implied warranty of
12*946379e7Schristos    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13*946379e7Schristos    GNU General Public License for more details.
14*946379e7Schristos 
15*946379e7Schristos    You should have received a copy of the GNU General Public License
16*946379e7Schristos    along with this program; if not, write to the Free Software Foundation,
17*946379e7Schristos    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
18*946379e7Schristos 
19*946379e7Schristos #ifdef HAVE_CONFIG_H
20*946379e7Schristos # include <config.h>
21*946379e7Schristos #endif
22*946379e7Schristos 
23*946379e7Schristos /* Specification.  */
24*946379e7Schristos #include "format.h"
25*946379e7Schristos 
26*946379e7Schristos #include <stdbool.h>
27*946379e7Schristos #include <stdio.h>
28*946379e7Schristos #include <stdlib.h>
29*946379e7Schristos 
30*946379e7Schristos #include "message.h"
31*946379e7Schristos #include "gettext.h"
32*946379e7Schristos 
33*946379e7Schristos #define _(str) gettext (str)
34*946379e7Schristos 
35*946379e7Schristos /* Table of all format string parsers.  */
36*946379e7Schristos struct formatstring_parser *formatstring_parsers[NFORMATS] =
37*946379e7Schristos {
38*946379e7Schristos   /* format_c */		&formatstring_c,
39*946379e7Schristos   /* format_objc */		&formatstring_objc,
40*946379e7Schristos   /* format_sh */		&formatstring_sh,
41*946379e7Schristos   /* format_python */		&formatstring_python,
42*946379e7Schristos   /* format_lisp */		&formatstring_lisp,
43*946379e7Schristos   /* format_elisp */		&formatstring_elisp,
44*946379e7Schristos   /* format_librep */		&formatstring_librep,
45*946379e7Schristos   /* format_scheme */		&formatstring_scheme,
46*946379e7Schristos   /* format_smalltalk */	&formatstring_smalltalk,
47*946379e7Schristos   /* format_java */		&formatstring_java,
48*946379e7Schristos   /* format_csharp */		&formatstring_csharp,
49*946379e7Schristos   /* format_awk */		&formatstring_awk,
50*946379e7Schristos   /* format_pascal */		&formatstring_pascal,
51*946379e7Schristos   /* format_ycp */		&formatstring_ycp,
52*946379e7Schristos   /* format_tcl */		&formatstring_tcl,
53*946379e7Schristos   /* format_perl */		&formatstring_perl,
54*946379e7Schristos   /* format_perl_brace */	&formatstring_perl_brace,
55*946379e7Schristos   /* format_php */		&formatstring_php,
56*946379e7Schristos   /* format_gcc_internal */	&formatstring_gcc_internal,
57*946379e7Schristos   /* format_qt */		&formatstring_qt,
58*946379e7Schristos   /* format_boost */		&formatstring_boost
59*946379e7Schristos };
60*946379e7Schristos 
61*946379e7Schristos /* Check whether both formats strings contain compatible format
62*946379e7Schristos    specifications.
63*946379e7Schristos    PLURAL_DISTRIBUTION is either NULL or an array of nplurals elements,
64*946379e7Schristos    PLURAL_DISTRIBUTION[j] being true if the value j appears to be assumed
65*946379e7Schristos    infinitely often by the plural formula.
66*946379e7Schristos    Return the number of errors that were seen.  */
67*946379e7Schristos int
check_msgid_msgstr_format(const char * msgid,const char * msgid_plural,const char * msgstr,size_t msgstr_len,const enum is_format is_format[NFORMATS],const unsigned char * plural_distribution,formatstring_error_logger_t error_logger)68*946379e7Schristos check_msgid_msgstr_format (const char *msgid, const char *msgid_plural,
69*946379e7Schristos 			   const char *msgstr, size_t msgstr_len,
70*946379e7Schristos 			   const enum is_format is_format[NFORMATS],
71*946379e7Schristos 			   const unsigned char *plural_distribution,
72*946379e7Schristos 			   formatstring_error_logger_t error_logger)
73*946379e7Schristos {
74*946379e7Schristos   int seen_errors = 0;
75*946379e7Schristos   size_t i;
76*946379e7Schristos   unsigned int j;
77*946379e7Schristos 
78*946379e7Schristos   /* We check only those messages for which the msgid's is_format flag
79*946379e7Schristos      is one of 'yes' or 'possible'.  We don't check msgids with is_format
80*946379e7Schristos      'no' or 'impossible', to obey the programmer's order.  We don't check
81*946379e7Schristos      msgids with is_format 'undecided' because that would introduce too
82*946379e7Schristos      many checks, thus forcing the programmer to add "xgettext: no-c-format"
83*946379e7Schristos      anywhere where a translator wishes to use a percent sign.  */
84*946379e7Schristos   for (i = 0; i < NFORMATS; i++)
85*946379e7Schristos     if (possible_format_p (is_format[i]))
86*946379e7Schristos       {
87*946379e7Schristos 	/* At runtime, we can assume the program passes arguments that
88*946379e7Schristos 	   fit well for msgid.  We must signal an error if msgstr wants
89*946379e7Schristos 	   more arguments that msgid accepts.
90*946379e7Schristos 	   If msgstr wants fewer arguments than msgid, it wouldn't lead
91*946379e7Schristos 	   to a crash at runtime, but we nevertheless give an error because
92*946379e7Schristos 	   1) this situation occurs typically after the programmer has
93*946379e7Schristos 	      added some arguments to msgid, so we must make the translator
94*946379e7Schristos 	      specially aware of it (more than just "fuzzy"),
95*946379e7Schristos 	   2) it is generally wrong if a translation wants to ignore
96*946379e7Schristos 	      arguments that are used by other translations.  */
97*946379e7Schristos 
98*946379e7Schristos 	struct formatstring_parser *parser = formatstring_parsers[i];
99*946379e7Schristos 	char *invalid_reason = NULL;
100*946379e7Schristos 	void *msgid_descr =
101*946379e7Schristos 	  parser->parse (msgid_plural != NULL ? msgid_plural : msgid,
102*946379e7Schristos 			 false, &invalid_reason);
103*946379e7Schristos 
104*946379e7Schristos 	if (msgid_descr != NULL)
105*946379e7Schristos 	  {
106*946379e7Schristos 	    char buf[18+1];
107*946379e7Schristos 	    const char *pretty_msgstr = "msgstr";
108*946379e7Schristos 	    bool has_plural_translations = (strlen (msgstr) + 1 < msgstr_len);
109*946379e7Schristos 	    const char *p_end = msgstr + msgstr_len;
110*946379e7Schristos 	    const char *p;
111*946379e7Schristos 
112*946379e7Schristos 	    for (p = msgstr, j = 0; p < p_end; p += strlen (p) + 1, j++)
113*946379e7Schristos 	      {
114*946379e7Schristos 		void *msgstr_descr;
115*946379e7Schristos 
116*946379e7Schristos 		if (msgid_plural != NULL)
117*946379e7Schristos 		  {
118*946379e7Schristos 		    sprintf (buf, "msgstr[%u]", j);
119*946379e7Schristos 		    pretty_msgstr = buf;
120*946379e7Schristos 		  }
121*946379e7Schristos 
122*946379e7Schristos 		msgstr_descr = parser->parse (p, true, &invalid_reason);
123*946379e7Schristos 
124*946379e7Schristos 		if (msgstr_descr != NULL)
125*946379e7Schristos 		  {
126*946379e7Schristos 		    /* Use strict checking (require same number of format
127*946379e7Schristos 		       directives on both sides) if the message has no plurals,
128*946379e7Schristos 		       or if msgid_plural exists but on the msgstr[] side
129*946379e7Schristos 		       there is only msgstr[0], or if plural_distribution[j]
130*946379e7Schristos 		       indicates that the variant applies to infinitely many
131*946379e7Schristos 		       values of N.
132*946379e7Schristos 		       Use relaxed checking when there are at least two
133*946379e7Schristos 		       msgstr[] forms and the plural_distribution array does
134*946379e7Schristos 		       not give more precise information.  */
135*946379e7Schristos 		    bool strict_checking =
136*946379e7Schristos 		      (msgid_plural == NULL
137*946379e7Schristos 		       || !has_plural_translations
138*946379e7Schristos 		       || (plural_distribution != NULL && plural_distribution[j]));
139*946379e7Schristos 
140*946379e7Schristos 		    if (parser->check (msgid_descr, msgstr_descr,
141*946379e7Schristos 				       strict_checking,
142*946379e7Schristos 				       error_logger, pretty_msgstr))
143*946379e7Schristos 		      seen_errors++;
144*946379e7Schristos 
145*946379e7Schristos 		    parser->free (msgstr_descr);
146*946379e7Schristos 		  }
147*946379e7Schristos 		else
148*946379e7Schristos 		  {
149*946379e7Schristos 		    error_logger (_("\
150*946379e7Schristos '%s' is not a valid %s format string, unlike 'msgid'. Reason: %s"),
151*946379e7Schristos 				  pretty_msgstr, format_language_pretty[i],
152*946379e7Schristos 				  invalid_reason);
153*946379e7Schristos 		    seen_errors++;
154*946379e7Schristos 		    free (invalid_reason);
155*946379e7Schristos 		  }
156*946379e7Schristos 	      }
157*946379e7Schristos 
158*946379e7Schristos 	    parser->free (msgid_descr);
159*946379e7Schristos 	  }
160*946379e7Schristos 	else
161*946379e7Schristos 	  free (invalid_reason);
162*946379e7Schristos       }
163*946379e7Schristos 
164*946379e7Schristos   return seen_errors;
165*946379e7Schristos }
166