1 /*
2     Checks the po file for C format correctness.
3     To compile it you need the gettext package installed
4     with its development files.
5     Compilation commandline:
6 
7       gcc -lgettextpo -o msgcheckformat msgcheckformat.c
8 
9     Usage:
10 
11       msgcheckformat <pofile> [ask|fix]
12 
13     ----------------------------------------------------------------------------
14 
15     Copyright (C) 2004 Szymon Stefanek (pragma at kvirc dot net),
16                   2010 Fabio Bas (ctrlaltca at gmail dot com)
17 
18     This program is free software; you can redistribute it and/or modify
19     it under the terms of the GNU General Public License as published by
20     the Free Software Foundation; either version 2, or (at your option)
21     any later version.
22 
23     This program is distributed in the hope that it will be useful,
24     but WITHOUT ANY WARRANTY; without even the implied warranty of
25     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26     GNU General Public License for more details.
27 */
28 
29 
30 
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <sys/types.h>
35 #include <unistd.h>
36 #include <gettext-po.h>
37 
38 int total_errors = 0;
39 int total_warnings = 0;
40 int total_remove = 0;
41 int total_ignore = 0;
42 int total_fix = 0;
43 int solution_mode = 0;
44 
po_xerror(int severity,po_message_t message,const char * filename,size_t lineno,size_t column,int multiline_p,const char * message_text)45 static void po_xerror( int severity, po_message_t message,
46                        const char *filename, size_t lineno, size_t column,
47                        int multiline_p, const char *message_text )
48 {
49     fprintf( stderr, "%s:%u:%u: %s\n",
50              filename, (unsigned int)lineno, (unsigned int)column, message_text );
51     if (severity) exit(1);
52 }
53 
po_xerror2(int severity,po_message_t message1,const char * filename1,size_t lineno1,size_t column1,int multiline_p1,const char * message_text1,po_message_t message2,const char * filename2,size_t lineno2,size_t column2,int multiline_p2,const char * message_text2)54 static void po_xerror2( int severity, po_message_t message1,
55                         const char *filename1, size_t lineno1, size_t column1,
56                         int multiline_p1, const char *message_text1,
57                         po_message_t message2,
58                         const char *filename2, size_t lineno2, size_t column2,
59                         int multiline_p2, const char *message_text2 )
60 {
61     fprintf( stderr, "%s:%u:%u: %s\n",
62              filename1, (unsigned int)lineno1, (unsigned int)column1, message_text1 );
63     fprintf( stderr, "%s:%u:%u: %s\n",
64              filename2, (unsigned int)lineno2, (unsigned int)column2, message_text2 );
65     if (severity) exit(1);
66 }
67 
68 static const struct po_xerror_handler po_xerror_handler = { po_xerror, po_xerror2 };
69 
replace_crlf(const char * msg,char * buf)70 static void replace_crlf(const char * msg,char * buf)
71 {
72 	while(*msg)
73 	{
74 		if(*msg == '\r')
75 		{
76 			*buf++ = '\\';
77 			*buf++ = 'r';
78 			msg++;
79 		} else if(*msg == '\n')
80 		{
81 			*buf++ = '\\';
82 			*buf++ = 'n';
83 			msg++;
84 		} else *buf++ = *msg++;
85 	}
86 }
87 
88 
print_error(po_message_t m,const char * msgid,const char * msgstr,const char * error,int solution)89 static int print_error(po_message_t m,const char * msgid,const char * msgstr,const char * error, int solution)
90 {
91 	char bufferid[16000];
92 	char bufferstr[16000];
93 	int s = 0;
94 	int is_fuzzy = po_message_is_fuzzy(m) ? 1 : 0;
95 	replace_crlf(msgid,bufferid);
96 	replace_crlf(msgstr,bufferstr);
97 	fprintf(stderr,"%s in %s:\n msgid: %s\n msgstr: %s\n reason: %s\n\n",
98 		is_fuzzy ? "Warning" : "Error",
99 		is_fuzzy ? "fuzzy entry" : "entry",
100 		bufferid,bufferstr,error);
101 	if(is_fuzzy)total_warnings++;
102 	else total_errors++;
103 	switch(solution_mode)
104 	{
105 		case 1:
106 			// ask the user
107 			switch(solution)
108 			{
109 				case 1:
110 					printf("Pick a solution: (i)gnore the problem, or (r)emove the offending translation:");
111 					while(s != 'i' && s != 'r') s = getchar();
112 					if(s == 'r')
113 					{
114 						// delete the wrong translation
115 						/* Change the msgstr (translation) of a message.
116 							Use an empty string to denote an untranslated message.  */
117 						po_message_set_msgstr(m, "");
118 						total_remove++;
119 					} else {
120 						total_ignore++;
121 					}
122 					break;
123 				case 2:
124 					printf("Pick a solution: (i)gnore the problem, (f)ix it or (r)emove the offending translation:");
125 					while(s != 'i' && s != 'f' && s != 'r') s = getchar();
126 					if(s == 'r')
127 					{
128 						// delete the wrong translation
129 						/* Change the msgstr (translation) of a message.
130 							Use an empty string to denote an untranslated message.  */
131 						po_message_set_msgstr(m, "");
132 						total_remove++;
133 					} else if(s == 'f') {
134 						total_fix++;
135 						return 1;
136 					} else {
137 						total_ignore++;
138 					}
139 					break;
140 			}
141 			break;
142 		case 2:
143 			// fix automatically
144 			switch(solution)
145 			{
146 				case 1:
147 					// delete the wrong translation
148 					/* Change the msgstr (translation) of a message.
149 						Use an empty string to denote an untranslated message.  */
150 					po_message_set_msgstr(m, "");
151 					total_remove++;
152 					break;
153 				case 2:
154 					total_fix++;
155 					return 1;
156 					break;
157 			}
158 			break;
159 		default:
160 			// do nothing
161 			break;
162 	}
163 	return 0;
164 }
165 
is_valid_fmtstr(const char * p)166 inline int is_valid_fmtstr(const char * p)
167 {
168 	if(!*p) return 0;
169 	if(*p != '%') return 0;
170 	p++;
171 	if(!*p) return 0;
172 	if((*p >= '1' && *p <= '9') ||
173 		(*p >= 'a' && *p <= 'z') ||
174 		(*p >= 'A' && *p <= 'Z'))
175 	{
176 		p--;
177 		return 1;
178 	}
179 	p--;
180 	return 0;
181 }
182 
process_message(po_message_t m)183 static void process_message(po_message_t m)
184 {
185 	const char * msgstr = po_message_msgstr(m);
186 	const char * msgid = po_message_msgid(m);
187 	const char *p;
188 	char *q;
189 	char *fixedstr;
190 
191 	if(msgid[0] == '\0')return;
192 	if(strlen(msgid) < 1)return;
193 	if(strlen(msgstr) < 1)return;
194 	if(msgstr[0] == '\0')return;
195 
196 	fixedstr = strdup(msgstr);
197 
198 	p = msgid;
199 	q = fixedstr;
200 
201 	while(*p && *q)
202 	{
203 		while(*p && !is_valid_fmtstr(p))p++;
204 		while(*q && !is_valid_fmtstr(q))q++;
205 		if(*q && *p)
206 		{
207 			q++;
208 			p++;
209 			if(*p != *q)
210 			{
211 				char buff[200];
212 				sprintf(buff,"Mismatched format character (%c != %c)",*p,*q);
213 				if(print_error(m,msgid,fixedstr,buff, 2))
214 				{
215 					//fix the format string
216 					*q=*p;
217 					po_message_set_msgstr(m, fixedstr);
218 				}
219 			}
220 		}
221 	}
222 
223 	if(*p || *q)
224 	{
225 		print_error(m,msgid,msgstr,"Mismatched number of format characters", 1);
226 	}
227 }
228 
process_messages(po_message_iterator_t it)229 static void process_messages(po_message_iterator_t it)
230 {
231 	po_message_t m = po_next_message(it);
232 	while(m)
233 	{
234 		process_message(m);
235 		m = po_next_message(it);
236 	}
237 }
238 
main(int argc,char ** argv)239 int main(int argc, char **argv)
240 {
241 	po_file_t po;
242 	po_message_iterator_t it;
243 	const char * const * domains;
244 
245 	if(!argv[1])
246 	{
247 		fprintf(stderr,"usage: %s <pofile> [ask|fix]\n",argv[0]);
248 		return 0;
249 	}
250 
251 	if(argv[2])
252 	{
253 		if(!strncmp(argv[2], "ask", 3))
254 		{
255 			solution_mode=1;
256 		} else if(!strncmp(argv[2], "fix", 3)) {
257 			solution_mode=2;
258 		} else {
259 			fprintf(stderr,"usage: %s <pofile> [ask|fix]\n",argv[0]);
260 			return 0;
261 		}
262 	}
263 
264 	po = po_file_read(argv[1], &po_xerror_handler);
265 
266 	if(!po)
267 	{
268 		fprintf(stderr,"Couldn't read the input po file\n");
269 		return 0;
270 	}
271 
272 	domains = po_file_domains(po);
273 
274 	if(!domains)
275 	{
276 		fprintf(stderr,"Couldn't find the message domains in the po file\n");
277 		return 0;
278 	}
279 
280 	while(*domains)
281 	{
282 		it = po_message_iterator(po,*domains);
283 		process_messages(it);
284 		po_message_iterator_free(it);
285 		domains++;
286 	}
287 
288 	if(total_errors == 0 && total_warnings == 0)
289 	{
290 		fprintf(stderr,"No errors found\n");
291 	} else if(solution_mode==0) {
292 		fprintf(stderr,"%d warnings, %d errors\n",total_warnings,total_errors);
293 	} else {
294 		fprintf(stderr,"%d warnings, %d errors, %d fixes, %d removed, %d ignored\n",total_warnings,total_errors, total_fix, total_remove, total_ignore);
295 		if(total_fix || total_remove)
296 		{
297 			int s = 0;
298 			printf("Commit changes to file? (y)es, (n)o:");
299 			while(s != 'y' && s != 'n') s = getchar();
300 			if(s == 'y')
301 			{
302 				if(po_file_write(po, argv[1], &po_xerror_handler)) printf("Changes committed.");
303 				else printf("Error writing file.");
304 			} else {
305 				printf("Changes discarded.");
306 			}
307 		}
308 	}
309 
310 	po_file_free(po);
311 
312 	return 0;
313 }
314