1
/* GNU gettext - internationalization aids
2
Copyright (C) 1995-1998, 2000, 2001 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)
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. */
32
#include "libgettext.h"
36
#define _(str) gettext (str)
39
/* This structure defines a derived class of the po_ty class. (See
40
po.h for an explanation.) */
41
typedef struct compare_class_ty compare_class_ty;
42
struct compare_class_ty
44
/* inherited instance variables, etc */
47
/* Name of domain we are currently examining. */
50
/* List of domains already appeared in the current file. */
51
string_list_ty *domain_list;
53
/* List of messages already appeared in the current file. */
57
/* String containing name the program is called with. */
58
const char *program_name;
61
static const struct option long_options[] =
63
{ "directory", required_argument, NULL, 'D' },
64
{ "help", no_argument, NULL, 'h' },
65
{ "version", no_argument, NULL, 'V' },
70
/* Prototypes for local functions. */
71
static void usage PARAMS ((int __status));
72
static void error_print PARAMS ((void));
73
static void compare PARAMS ((char *, char *));
74
static message_list_ty *grammar PARAMS ((char *__filename));
75
static void compare_constructor PARAMS ((po_ty *__that));
76
static void compare_destructor PARAMS ((po_ty *__that));
77
static void compare_directive_domain PARAMS ((po_ty *__that, char *__name));
78
static void compare_directive_message PARAMS ((po_ty *__that, char *__msgid,
79
lex_pos_ty *msgid_pos,
83
lex_pos_ty *__msgstr_pos));
84
static void compare_parse_debrief PARAMS ((po_ty *__that));
96
/* Set program name for messages. */
97
program_name = argv[0];
98
error_print_progname = error_print;
100
#ifdef HAVE_SETLOCALE
101
/* Set locale via LC_ALL. */
102
setlocale (LC_ALL, "");
105
/* Set the text message domain. */
106
bindtextdomain (PACKAGE, LOCALEDIR);
107
textdomain (PACKAGE);
111
while ((optchar = getopt_long (argc, argv, "D:hV", long_options, NULL))
115
case '\0': /* long option */
119
dir_list_append (optarg);
131
usage (EXIT_FAILURE);
135
/* Version information is requested. */
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, 2001");
145
printf (_("Written by %s.\n"), "Peter Miller");
149
/* Help is requested. */
151
usage (EXIT_SUCCESS);
153
/* Test whether we have an .po file name as argument. */
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]);
171
/* Display usage information and exit. */
176
if (status != EXIT_SUCCESS)
177
fprintf (stderr, _("Try `%s --help' for more information.\n"),
181
/* xgettext: no-wrap */
183
Usage: %s [OPTION] def.po ref.po\n\
184
Mandatory arguments to long options are mandatory for short options too.\n\
185
-D, --directory=DIRECTORY add DIRECTORY to list for input files search\n\
186
-h, --help display this help and exit\n\
187
-V, --version output version information and exit\n\
189
Compare two Uniforum style .po files to check that both contain the same\n\
190
set of msgid strings. The def.po file is an existing PO file with the\n\
191
old translations. The ref.po file is the last created PO file\n\
192
(generally by xgettext). This is useful for checking that you have\n\
193
translated each and every message in your program. Where an exact match\n\
194
cannot be found, fuzzy matching is used to produce better diagnostics.\n"),
196
fputs (_("Report bugs to <bug-gnu-utils@gnu.org>.\n"), stdout);
203
/* The address of this function will be assigned to the hook in the error
208
/* We don't want the program name to be printed in messages. Emacs'
209
compile.el does not like this. */
218
message_list_ty *list1;
219
message_list_ty *list2;
224
/* This is the master file, created by a human. */
225
list1 = grammar (fn1);
227
/* This is the generated file, created by groping the sources with
228
the xgettext program. */
229
list2 = grammar (fn2);
231
/* Every entry in the xgettext generated file must be matched by a
232
(single) entry in the human created file. */
234
for (j = 0; j < list2->nitems; ++j)
238
mp2 = list2->item[j];
240
/* See if it is in the other file. */
241
mp1 = message_list_search (list1, mp2->msgid);
248
/* If the message was not defined at all, try to find a very
249
similar message, it could be a typo, or the suggestion may
252
mp1 = message_list_search_fuzzy (list1, mp2->msgid);
255
po_gram_error_at_line (&mp2->variant[0].pos, _("\
256
this message is used but not defined..."));
257
po_gram_error_at_line (&mp1->variant[0].pos, _("\
258
...but this definition is similar"));
263
po_gram_error_at_line (&mp2->variant[0].pos, _("\
264
this message is used but not defined in %s"), fn1);
268
/* Look for messages in the human generated file, which are not
269
present in the xgettext generated file, indicating messages which
270
are not used in the program. */
271
for (k = 0; k < list1->nitems; ++k)
273
mp1 = list1->item[k];
276
po_gram_error_at_line (&mp1->variant[0].pos,
277
_("warning: this message is not used"));
280
/* Exit with status 1 on any error. */
282
error (EXIT_FAILURE, 0,
283
ngettext ("found %d fatal error", "found %d fatal errors", nerrors),
288
/* Local functions. */
291
compare_constructor (that)
294
compare_class_ty *this = (compare_class_ty *) that;
296
this->mlp = message_list_alloc ();
297
this->domain = MESSAGE_DOMAIN_DEFAULT;
298
this->domain_list = string_list_alloc ();
303
compare_destructor (that)
306
compare_class_ty *this = (compare_class_ty *) that;
308
string_list_free (this->domain_list);
309
/* Do not free this->mlp! */
314
compare_directive_domain (that, name)
318
compare_class_ty *this = (compare_class_ty *)that;
319
/* Override current domain name. Don't free memory. */
325
compare_directive_message (that, msgid, msgid_pos, msgid_plural,
326
msgstr, msgstr_len, msgstr_pos)
329
lex_pos_ty *msgid_pos;
333
lex_pos_ty *msgstr_pos;
335
compare_class_ty *this = (compare_class_ty *) that;
337
message_variant_ty *mvp;
339
/* Remember the domain names for later. */
340
string_list_append_unique (this->domain_list, this->domain);
342
/* See if this message ID has been seen before. */
343
mp = message_list_search (this->mlp, msgid);
348
mp = message_alloc (msgid, msgid_plural);
349
message_list_append (this->mlp, mp);
352
/* See if this domain has been seen for this message ID. */
353
mvp = message_variant_search (mp, this->domain);
356
po_gram_error_at_line (msgid_pos, _("duplicate message definition"));
357
po_gram_error_at_line (&mvp->pos, _("\
358
...this is the location of the first definition"));
362
message_variant_append (mp, this->domain, msgstr, msgstr_len, msgstr_pos);
367
compare_parse_debrief (that)
370
compare_class_ty *this = (compare_class_ty *) that;
371
message_list_ty *mlp = this->mlp;
374
/* For each domain in the used-domain-list, make sure each message
375
defines a msgstr in that domain. */
376
for (j = 0; j < this->domain_list->nitems; ++j)
378
const char *domain_name;
381
domain_name = this->domain_list->item[j];
382
for (k = 0; k < mlp->nitems; ++k)
384
const message_ty *mp;
388
for (m = 0; m < mp->variant_count; ++m)
390
message_variant_ty *mvp;
392
mvp = &mp->variant[m];
393
if (strcmp (domain_name, mvp->domain) == 0)
396
if (m >= mp->variant_count)
397
po_gram_error_at_line (&mp->variant[0].pos, _("\
398
this message has no definition in the \"%s\" domain"), domain_name);
404
/* So that the one parser can be used for multiple programs, and also
405
use good data hiding and encapsulation practices, an object
406
oriented approach has been taken. An object instance is allocated,
407
and all actions resulting from the parse will be through
408
invokations of method functions of that object. */
410
static po_method_ty compare_methods =
412
sizeof (compare_class_ty),
415
compare_directive_domain,
416
compare_directive_message,
417
NULL, /* parse_brief */
418
compare_parse_debrief,
420
NULL, /* comment_dot */
421
NULL, /* comment_filepos */
422
NULL, /* comment_special */
426
static message_list_ty *
431
message_list_ty *mlp;
433
pop = po_alloc (&compare_methods);
434
po_scan (pop, filename);
435
mlp = ((compare_class_ty *)pop)->mlp;