No empty .Rs/.Re
[netbsd-mini2440.git] / gnu / dist / gettext / gettext-tools / src / msgcmp.c
blob01776602958cc143f781ca56d5e4d39ac8b7a89a
1 /* GNU gettext - internationalization aids
2 Copyright (C) 1995-1998, 2000-2005 Free Software Foundation, Inc.
3 This file was written by Peter Miller <millerp@canb.auug.org.au>
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2, or (at your option)
8 any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software Foundation,
17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
19 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
23 #include <getopt.h>
24 #include <limits.h>
25 #include <stdbool.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <locale.h>
30 #include "closeout.h"
31 #include "dir-list.h"
32 #include "error.h"
33 #include "error-progname.h"
34 #include "progname.h"
35 #include "relocatable.h"
36 #include "basename.h"
37 #include "message.h"
38 #include "exit.h"
39 #include "read-po.h"
40 #include "msgl-iconv.h"
41 #include "strstr.h"
42 #include "c-strcase.h"
43 #include "gettext.h"
45 #define _(str) gettext (str)
48 /* Apply the .pot file to each of the domains in the PO file. */
49 static bool multi_domain_mode = false;
51 /* Long options. */
52 static const struct option long_options[] =
54 { "directory", required_argument, NULL, 'D' },
55 { "help", no_argument, NULL, 'h' },
56 { "multi-domain", no_argument, NULL, 'm' },
57 { "properties-input", no_argument, NULL, 'P' },
58 { "stringtable-input", no_argument, NULL, CHAR_MAX + 1 },
59 { "version", no_argument, NULL, 'V' },
60 { NULL, 0, NULL, 0 }
64 /* Forward declaration of local functions. */
65 static void usage (int status)
66 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
67 __attribute__ ((noreturn))
68 #endif
70 static void compare (const char *fn1, const char *fn2);
73 int
74 main (int argc, char *argv[])
76 int optchar;
77 bool do_help;
78 bool do_version;
80 /* Set program name for messages. */
81 set_program_name (argv[0]);
82 error_print_progname = maybe_print_progname;
83 gram_max_allowed_errors = UINT_MAX;
85 #ifdef HAVE_SETLOCALE
86 /* Set locale via LC_ALL. */
87 setlocale (LC_ALL, "");
88 #endif
90 /* Set the text message domain. */
91 bindtextdomain (PACKAGE, relocate (LOCALEDIR));
92 textdomain (PACKAGE);
94 /* Ensure that write errors on stdout are detected. */
95 atexit (close_stdout);
97 do_help = false;
98 do_version = false;
99 while ((optchar = getopt_long (argc, argv, "D:hmPV", long_options, NULL))
100 != EOF)
101 switch (optchar)
103 case '\0': /* long option */
104 break;
106 case 'D':
107 dir_list_append (optarg);
108 break;
110 case 'h':
111 do_help = true;
112 break;
114 case 'm':
115 multi_domain_mode = true;
116 break;
118 case 'P':
119 input_syntax = syntax_properties;
120 break;
122 case 'V':
123 do_version = true;
124 break;
126 case CHAR_MAX + 1: /* --stringtable-input */
127 input_syntax = syntax_stringtable;
128 break;
130 default:
131 usage (EXIT_FAILURE);
132 break;
135 /* Version information is requested. */
136 if (do_version)
138 printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
139 /* xgettext: no-wrap */
140 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
141 This is free software; see the source for copying conditions. There is NO\n\
142 warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
144 "1995-1998, 2000-2005");
145 printf (_("Written by %s.\n"), "Peter Miller");
146 exit (EXIT_SUCCESS);
149 /* Help is requested. */
150 if (do_help)
151 usage (EXIT_SUCCESS);
153 /* Test whether we have an .po file name as argument. */
154 if (optind >= argc)
156 error (EXIT_SUCCESS, 0, _("no input files given"));
157 usage (EXIT_FAILURE);
159 if (optind + 2 != argc)
161 error (EXIT_SUCCESS, 0, _("exactly 2 input files required"));
162 usage (EXIT_FAILURE);
165 /* compare the two files */
166 compare (argv[optind], argv[optind + 1]);
167 exit (EXIT_SUCCESS);
171 /* Display usage information and exit. */
172 static void
173 usage (int status)
175 if (status != EXIT_SUCCESS)
176 fprintf (stderr, _("Try `%s --help' for more information.\n"),
177 program_name);
178 else
180 printf (_("\
181 Usage: %s [OPTION] def.po ref.pot\n\
182 "), program_name);
183 printf ("\n");
184 /* xgettext: no-wrap */
185 printf (_("\
186 Compare two Uniforum style .po files to check that both contain the same\n\
187 set of msgid strings. The def.po file is an existing PO file with the\n\
188 translations. The ref.pot file is the last created PO file, or a PO Template\n\
189 file (generally created by xgettext). This is useful for checking that\n\
190 you have translated each and every message in your program. Where an exact\n\
191 match cannot be found, fuzzy matching is used to produce better diagnostics.\n\
192 "));
193 printf ("\n");
194 printf (_("\
195 Mandatory arguments to long options are mandatory for short options too.\n"));
196 printf ("\n");
197 printf (_("\
198 Input file location:\n"));
199 printf (_("\
200 def.po translations\n"));
201 printf (_("\
202 ref.pot references to the sources\n"));
203 printf (_("\
204 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
205 printf ("\n");
206 printf (_("\
207 Operation modifiers:\n"));
208 printf (_("\
209 -m, --multi-domain apply ref.pot to each of the domains in def.po\n"));
210 printf ("\n");
211 printf (_("\
212 Input file syntax:\n"));
213 printf (_("\
214 -P, --properties-input input files are in Java .properties syntax\n"));
215 printf (_("\
216 --stringtable-input input files are in NeXTstep/GNUstep .strings\n\
217 syntax\n"));
218 printf ("\n");
219 printf (_("\
220 Informative output:\n"));
221 printf (_("\
222 -h, --help display this help and exit\n"));
223 printf (_("\
224 -V, --version output version information and exit\n"));
225 printf ("\n");
226 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), stdout);
229 exit (status);
233 /* Return true if a message should be kept. */
234 static bool
235 is_message_selected (const message_ty *mp)
237 /* Always keep the header entry. */
238 if (mp->msgid[0] == '\0')
239 return true;
241 return !mp->obsolete;
245 /* Remove obsolete messages from a message list. Return the modified list. */
246 static msgdomain_list_ty *
247 remove_obsoletes (msgdomain_list_ty *mdlp)
249 size_t k;
251 for (k = 0; k < mdlp->nitems; k++)
252 message_list_remove_if_not (mdlp->item[k]->messages, is_message_selected);
254 return mdlp;
258 static void
259 match_domain (const char *fn1, const char *fn2,
260 message_list_ty *defmlp, message_list_ty *refmlp,
261 int *nerrors)
263 size_t j;
265 for (j = 0; j < refmlp->nitems; j++)
267 message_ty *refmsg;
268 message_ty *defmsg;
270 refmsg = refmlp->item[j];
272 /* See if it is in the other file. */
273 defmsg = message_list_search (defmlp, refmsg->msgid);
274 if (defmsg)
275 defmsg->used = 1;
276 else
278 /* If the message was not defined at all, try to find a very
279 similar message, it could be a typo, or the suggestion may
280 help. */
281 (*nerrors)++;
282 defmsg = message_list_search_fuzzy (defmlp, refmsg->msgid);
283 if (defmsg)
285 po_gram_error_at_line (&refmsg->pos, _("\
286 this message is used but not defined..."));
287 po_gram_error_at_line (&defmsg->pos, _("\
288 ...but this definition is similar"));
289 defmsg->used = 1;
291 else
292 po_gram_error_at_line (&refmsg->pos, _("\
293 this message is used but not defined in %s"), fn1);
299 static void
300 compare (const char *fn1, const char *fn2)
302 msgdomain_list_ty *def;
303 msgdomain_list_ty *ref;
304 int nerrors;
305 size_t j, k;
306 message_list_ty *empty_list;
308 /* This is the master file, created by a human. */
309 def = remove_obsoletes (read_po_file (fn1));
311 /* This is the generated file, created by groping the sources with
312 the xgettext program. */
313 ref = remove_obsoletes (read_po_file (fn2));
315 /* The references file can be either in ASCII or in UTF-8. If it is
316 in UTF-8, we have to convert the definitions to UTF-8 as well. */
318 bool was_utf8 = false;
319 for (k = 0; k < ref->nitems; k++)
321 message_list_ty *mlp = ref->item[k]->messages;
323 for (j = 0; j < mlp->nitems; j++)
324 if (mlp->item[j]->msgid[0] == '\0' /* && !mlp->item[j]->obsolete */)
326 const char *header = mlp->item[j]->msgstr;
328 if (header != NULL)
330 const char *charsetstr = strstr (header, "charset=");
332 if (charsetstr != NULL)
334 size_t len;
336 charsetstr += strlen ("charset=");
337 len = strcspn (charsetstr, " \t\n");
338 if (len == strlen ("UTF-8")
339 && c_strncasecmp (charsetstr, "UTF-8", len) == 0)
340 was_utf8 = true;
345 if (was_utf8)
346 def = iconv_msgdomain_list (def, "UTF-8", fn1);
349 empty_list = message_list_alloc (false);
351 /* Every entry in the xgettext generated file must be matched by a
352 (single) entry in the human created file. */
353 nerrors = 0;
354 if (!multi_domain_mode)
355 for (k = 0; k < ref->nitems; k++)
357 const char *domain = ref->item[k]->domain;
358 message_list_ty *refmlp = ref->item[k]->messages;
359 message_list_ty *defmlp;
361 defmlp = msgdomain_list_sublist (def, domain, false);
362 if (defmlp == NULL)
363 defmlp = empty_list;
365 match_domain (fn1, fn2, defmlp, refmlp, &nerrors);
367 else
369 /* Apply the references messages in the default domain to each of
370 the definition domains. */
371 message_list_ty *refmlp = ref->item[0]->messages;
373 for (k = 0; k < def->nitems; k++)
375 message_list_ty *defmlp = def->item[k]->messages;
377 /* Ignore the default message domain if it has no messages. */
378 if (k > 0 || defmlp->nitems > 0)
379 match_domain (fn1, fn2, defmlp, refmlp, &nerrors);
383 /* Look for messages in the definition file, which are not present
384 in the reference file, indicating messages which defined but not
385 used in the program. */
386 for (k = 0; k < def->nitems; ++k)
388 message_list_ty *defmlp = def->item[k]->messages;
390 for (j = 0; j < defmlp->nitems; j++)
392 message_ty *defmsg = defmlp->item[j];
394 if (!defmsg->used)
395 po_gram_error_at_line (&defmsg->pos,
396 _("warning: this message is not used"));
400 /* Exit with status 1 on any error. */
401 if (nerrors > 0)
402 error (EXIT_FAILURE, 0,
403 ngettext ("found %d fatal error", "found %d fatal errors", nerrors),
404 nerrors);