1
/* Extracts strings from C source file to Uniforum style .po file.
2
Copyright (C) 1995-1998, 2000, 2001 Free Software Foundation, Inc.
3
Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995.
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. */
26
#include <sys/param.h>
30
#include <sys/types.h>
51
#include "printf-parse.h"
53
#include "libgettext.h"
55
#ifndef _POSIX_VERSION
56
struct passwd *getpwuid ();
60
/* A convenience macro. I don't like writing gettext() every time. */
61
#define _(str) gettext (str)
64
/* If nonzero add all comments immediately preceding one of the keywords. */
65
static int add_all_comments;
67
/* If nonzero add comments for file name and line number for each msgid. */
68
static int line_comment;
70
/* Tag used in comment of prevailing domain. */
71
static char *comment_tag;
73
/* Name of default domain file. If not set defaults to messages.po. */
74
static char *default_domain;
76
/* If called with --debug option the output reflects whether format
77
string recognition is done automatically or forced by the user. */
80
/* Content of .po files with symbols to be excluded. */
81
static message_list_ty *exclude;
83
/* If nonzero extract all strings. */
84
static int extract_all;
86
/* Force output of PO file even if empty. */
89
/* If nonzero a non GNU related user wants to use this. Omit the FSF
90
copyright in the output. */
91
static int foreign_user;
93
/* String used as prefix for msgstr. */
94
static char *msgstr_prefix;
96
/* String used as suffix for msgstr. */
97
static char *msgstr_suffix;
99
/* Directory in which output files are created. */
100
static char *output_dir;
102
/* If nonzero omit header with information about this run. */
103
static int omit_header;
105
/* String containing name the program is called with. */
106
const char *program_name;
109
static const struct option long_options[] =
111
{ "add-comments", optional_argument, NULL, 'c' },
112
{ "add-location", no_argument, &line_comment, 1 },
113
{ "c++", no_argument, NULL, 'C' },
114
{ "debug", no_argument, &do_debug, 1 },
115
{ "default-domain", required_argument, NULL, 'd' },
116
{ "directory", required_argument, NULL, 'D' },
117
{ "escape", no_argument, NULL, 'E' },
118
{ "exclude-file", required_argument, NULL, 'x' },
119
{ "extract-all", no_argument, &extract_all, 1 },
120
{ "files-from", required_argument, NULL, 'f' },
121
{ "force-po", no_argument, &force_po, 1 },
122
{ "foreign-user", no_argument, &foreign_user, 1 },
123
{ "help", no_argument, NULL, 'h' },
124
{ "indent", no_argument, NULL, 'i' },
125
{ "join-existing", no_argument, NULL, 'j' },
126
{ "keyword", optional_argument, NULL, 'k' },
127
{ "language", required_argument, NULL, 'L' },
128
{ "msgstr-prefix", optional_argument, NULL, 'm' },
129
{ "msgstr-suffix", optional_argument, NULL, 'M' },
130
{ "no-escape", no_argument, NULL, 'e' },
131
{ "no-location", no_argument, &line_comment, 0 },
132
{ "omit-header", no_argument, &omit_header, 1 },
133
{ "output", required_argument, NULL, 'o' },
134
{ "output-dir", required_argument, NULL, 'p' },
135
{ "sort-by-file", no_argument, NULL, 'F' },
136
{ "sort-output", no_argument, NULL, 's' },
137
{ "strict", no_argument, NULL, 'S' },
138
{ "string-limit", required_argument, NULL, 'l' },
139
{ "trigraphs", no_argument, NULL, 'T' },
140
{ "version", no_argument, NULL, 'V' },
141
{ "width", required_argument, NULL, 'w', },
146
/* Prototypes for local functions. */
147
static void usage PARAMS ((int status))
148
#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ > 4) || __GNUC__ > 2)
149
__attribute__ ((noreturn))
152
static void error_print PARAMS ((void));
153
static string_list_ty *read_name_from_file PARAMS ((const char *__file_name));
154
static void exclude_directive_domain PARAMS ((po_ty *__pop, char *__name));
155
static void exclude_directive_message PARAMS ((po_ty *__pop, char *__msgid,
156
lex_pos_ty *__msgid_pos,
157
char *__msgid_plural,
160
lex_pos_ty *__msgstr_pos));
161
static void read_exclusion_file PARAMS ((char *__file_name));
162
static message_ty *remember_a_message PARAMS ((message_list_ty *__mlp,
163
xgettext_token_ty *__tp));
164
static void remember_a_message_plural PARAMS ((message_ty *__mp,
165
xgettext_token_ty *__tp));
166
static void scan_c_file PARAMS ((const char *__file_name,
167
message_list_ty *__mlp));
168
static void extract_constructor PARAMS ((po_ty *__that));
169
static void extract_directive_domain PARAMS ((po_ty *__that, char *__name));
170
static void extract_directive_message PARAMS ((po_ty *__that, char *__msgid,
171
lex_pos_ty *__msgid_pos,
172
char *__msgid_plural,
175
lex_pos_ty *__msgstr_pos));
176
static void extract_parse_brief PARAMS ((po_ty *__that));
177
static void extract_comment PARAMS ((po_ty *__that, const char *__s));
178
static void extract_comment_dot PARAMS ((po_ty *__that, const char *__s));
179
static void extract_comment_filepos PARAMS ((po_ty *__that, const char *__name,
181
static void extract_comment_special PARAMS ((po_ty *that, const char *s));
182
static void read_po_file PARAMS ((const char *__file_name,
183
message_list_ty *__mlp));
184
static long difftm PARAMS ((const struct tm *__a, const struct tm *__b));
185
static message_ty *construct_header PARAMS ((void));
186
static enum is_c_format test_whether_c_format PARAMS ((const char *__s));
189
/* The scanners must all be functions returning void and taking one
190
string argument and a message list argument. */
191
typedef void (*scanner_fp) PARAMS ((const char *, message_list_ty *));
193
static const char *extension_to_language PARAMS ((const char *));
194
static scanner_fp language_to_scanner PARAMS ((const char *));
206
message_list_ty *mlp;
207
int join_existing = 0;
209
int sort_by_file = 0;
211
const char *files_from = NULL;
212
string_list_ty *file_list;
213
char *output_file = NULL;
214
scanner_fp scanner = NULL;
216
/* Set program name for messages. */
217
program_name = argv[0];
218
error_print_progname = error_print;
220
#ifdef HAVE_SETLOCALE
221
/* Set locale via LC_ALL. */
222
setlocale (LC_ALL, "");
225
/* Set the text message domain. */
226
bindtextdomain (PACKAGE, LOCALEDIR);
227
textdomain (PACKAGE);
229
/* Set initial value of variables. */
231
default_domain = MESSAGE_DOMAIN_DEFAULT;
233
while ((optchar = getopt_long (argc, argv,
234
"ac::Cd:D:eEf:Fhijk::l:L:m::M::no:p:sTVw:x:",
235
long_options, NULL)) != EOF)
238
case '\0': /* Long option. */
246
add_all_comments = 1;
251
add_all_comments = 0;
252
comment_tag = optarg;
253
/* We ignore leading white space. */
254
while (isspace (*comment_tag))
259
scanner = language_to_scanner ("C++");
262
default_domain = optarg;
265
dir_list_append (optarg);
268
message_print_style_escape (0);
271
message_print_style_escape (1);
283
message_print_style_indent ();
289
if (optarg == NULL || *optarg != '\0')
290
xgettext_lex_keyword (optarg);
293
/* Accepted for backward compatibility with 0.10.35. */
296
scanner = language_to_scanner (optarg);
299
/* -m takes an optional argument. If none is given "" is assumed. */
300
msgstr_prefix = optarg == NULL ? "" : optarg;
303
/* -M takes an optional argument. If none is given "" is assumed. */
304
msgstr_suffix = optarg == NULL ? "" : optarg;
310
output_file = optarg;
314
size_t len = strlen (optarg);
316
if (output_dir != NULL)
319
if (optarg[len - 1] == '/')
320
output_dir = xstrdup (optarg);
323
asprintf (&output_dir, "%s/", optarg);
324
if (output_dir == NULL)
325
/* We are about to construct the absolute path to the
326
directory for the output files but asprintf failed. */
327
error (EXIT_FAILURE, errno, _("while preparing output"));
335
message_print_style_uniforum ();
338
xgettext_lex_trigraphs ();
347
value = strtol (optarg, &endp, 10);
349
message_page_width_set (value);
353
read_exclusion_file (optarg);
356
usage (EXIT_FAILURE);
360
/* Normalize selected options. */
361
if (omit_header != 0 && line_comment < 0)
364
if (!line_comment && sort_by_file)
365
error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
366
"--no-location", "--sort-by-file");
368
if (sort_output && sort_by_file)
369
error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
370
"--sort-output", "--sort-by-file");
372
if (join_existing && strcmp (default_domain, "-") == 0)
373
error (EXIT_FAILURE, 0, _("\
374
--join-existing cannot be used when output is written to stdout"));
376
if (!xgettext_any_keywords ())
379
xgettext cannot work without keywords to look for"));
380
usage (EXIT_FAILURE);
383
/* Version information requested. */
386
printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
387
/* xgettext: no-wrap */
388
printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
389
This is free software; see the source for copying conditions. There is NO\n\
390
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
392
"1995-1998, 2000, 2001");
393
printf (_("Written by %s.\n"), "Ulrich Drepper");
397
/* Help is requested. */
399
usage (EXIT_SUCCESS);
401
/* Test whether we have some input files given. */
402
if (files_from == NULL && optind >= argc)
404
error (EXIT_SUCCESS, 0, _("no input file given"));
405
usage (EXIT_FAILURE);
408
/* Canonize msgstr prefix/suffix. */
409
if (msgstr_prefix != NULL && msgstr_suffix == NULL)
411
else if (msgstr_prefix == NULL && msgstr_suffix != NULL)
412
msgstr_prefix = NULL;
414
/* Default output directory is the current directory. */
415
if (output_dir == NULL)
418
/* Construct the name of the output file. If the default domain has
419
the special name "-" we write to stdout. */
422
if (IS_ABSOLUTE_PATH (output_file) || strcmp (output_file, "-") == 0)
423
file_name = xstrdup (output_file);
425
/* Please do NOT add a .po suffix! */
426
file_name = concatenated_pathname (output_dir, output_file, NULL);
428
else if (strcmp (default_domain, "-") == 0)
431
file_name = concatenated_pathname (output_dir, default_domain, ".po");
433
/* Determine list of files we have to process. */
434
if (files_from != NULL)
435
file_list = read_name_from_file (files_from);
437
file_list = string_list_alloc ();
438
/* Append names from command line. */
439
for (cnt = optind; cnt < argc; ++cnt)
440
string_list_append_unique (file_list, argv[cnt]);
442
/* Allocate a message list to remember all the messages. */
443
mlp = message_list_alloc ();
445
/* Generate a header, so that we know how and when this PO file was
448
message_list_append (mlp, construct_header ());
450
/* Read in the old messages, so that we can add to them. */
452
read_po_file (file_name, mlp);
454
/* Process all input files. */
455
for (cnt = 0; cnt < file_list->nitems; ++cnt)
458
scanner_fp scan_file;
460
fname = file_list->item[cnt];
466
const char *extension;
467
const char *language;
469
/* Work out what the file extension is. */
470
extension = strrchr (fname, '/');
473
extension = strrchr (extension, '.');
479
/* derive the language from the extension, and the scanner
480
function from the language. */
481
language = extension_to_language (extension);
482
if (language == NULL)
485
warning: file `%s' extension `%s' is unknown; will try C"), fname, extension);
488
scan_file = language_to_scanner (language);
492
scan_file (fname, mlp);
494
string_list_free (file_list);
496
/* Sorting the list of messages. */
498
message_list_sort_by_filepos (mlp);
499
else if (sort_output)
500
message_list_sort_by_msgid (mlp);
502
/* Write the PO file. */
503
message_list_print (mlp, file_name, force_po, do_debug);
509
/* Display usage information and exit. */
514
if (status != EXIT_SUCCESS)
515
fprintf (stderr, _("Try `%s --help' for more information.\n"),
519
/* xgettext: no-wrap */
521
Usage: %s [OPTION] INPUTFILE ...\n\
522
Extract translatable string from given input files.\n\
524
Mandatory arguments to long options are mandatory for short options too.\n\
525
-a, --extract-all extract all strings\n\
526
-c, --add-comments[=TAG] place comment block with TAG (or those\n\
527
preceding keyword lines) in output file\n\
528
-C, --c++ shorthand for --language=C++\n\
529
--debug more detailed formatstring recognision result\n\
530
-d, --default-domain=NAME use NAME.po for output (instead of messages.po)\n\
531
-D, --directory=DIRECTORY add DIRECTORY to list for input files search\n\
532
-e, --no-escape do not use C escapes in output (default)\n\
533
-E, --escape use C escapes in output, no extended chars\n\
534
-f, --files-from=FILE get list of input files from FILE\n\
535
--force-po write PO file even if empty\n\
536
--foreign-user omit FSF copyright in output for foreign user\n\
537
-F, --sort-by-file sort output by file location\n"),
539
/* xgettext: no-wrap */
541
-h, --help display this help and exit\n\
542
-i, --indent write the .po file using indented style\n\
543
-j, --join-existing join messages with existing file\n\
544
-k, --keyword[=WORD] additonal keyword to be looked for (without\n\
545
WORD means not to use default keywords)\n\
546
-L, --language=NAME recognise the specified language (C, C++, PO),\n\
547
otherwise is guessed from file extension\n\
548
-m, --msgstr-prefix[=STRING] use STRING or \"\" as prefix for msgstr entries\n\
549
-M, --msgstr-suffix[=STRING] use STRING or \"\" as suffix for msgstr entries\n\
550
--no-location do not write '#: filename:line' lines\n"));
551
/* xgettext: no-wrap */
553
-n, --add-location generate '#: filename:line' lines (default)\n\
554
--omit-header don't write header with `msgid \"\"' entry\n\
555
-o, --output=FILE write output to specified file\n\
556
-p, --output-dir=DIR output files will be placed in directory DIR\n\
557
-s, --sort-output generate sorted output and remove duplicates\n\
558
--strict write out strict Uniforum conforming .po file\n\
559
-T, --trigraphs understand ANSI C trigraphs for input\n\
560
-V, --version output version information and exit\n\
561
-w, --width=NUMBER set output page width\n\
562
-x, --exclude-file=FILE entries from FILE are not extracted\n\
564
If INPUTFILE is -, standard input is read.\n"), stdout);
565
fputs (_("Report bugs to <bug-gnu-utils@gnu.org>.\n"),
573
/* The address of this function will be assigned to the hook in the error
578
/* We don't want the program name to be printed in messages. */
582
/* Read list of files to process from file. */
583
static string_list_ty *
584
read_name_from_file (file_name)
585
const char *file_name;
588
char *line_buf = NULL;
590
string_list_ty *result;
592
if (strcmp (file_name, "-") == 0)
596
fp = fopen (file_name, "r");
598
error (EXIT_FAILURE, errno,
599
_("error while opening \"%s\" for reading"), file_name);
602
result = string_list_alloc ();
606
/* Read next line from file. */
607
int len = getline (&line_buf, &line_len, fp);
609
/* In case of an error leave loop. */
613
/* Remove trailing '\n'. */
614
if (len > 0 && line_buf[len - 1] == '\n')
615
line_buf[--len] = '\0';
617
/* Test if we have to ignore the line. */
618
if (*line_buf == '\0' || *line_buf == '#')
621
string_list_append_unique (result, line_buf);
624
/* Free buffer allocated through getline. */
625
if (line_buf != NULL)
628
/* Close input stream. */
637
exclude_directive_domain (pop, name)
641
po_gram_error (_("this file may not contain domain directives"));
646
exclude_directive_message (pop, msgid, msgid_pos, msgid_plural,
647
msgstr, msgstr_len, msgstr_pos)
650
lex_pos_ty *msgid_pos;
654
lex_pos_ty *msgstr_pos;
658
/* See if this message ID has been seen before. */
660
exclude = message_list_alloc ();
661
mp = message_list_search (exclude, msgid);
666
mp = message_alloc (msgid, msgid_plural);
667
/* Do not free msgid. */
668
message_list_append (exclude, mp);
671
/* All we care about is the msgid. Throw the msgstr away.
672
Don't even check for duplicate msgids. */
677
/* So that the one parser can be used for multiple programs, and also
678
use good data hiding and encapsulation practices, an object
679
oriented approach has been taken. An object instance is allocated,
680
and all actions resulting from the parse will be through
681
invocations of method functions of that object. */
683
static po_method_ty exclude_methods =
686
NULL, /* constructor */
687
NULL, /* destructor */
688
exclude_directive_domain,
689
exclude_directive_message,
690
NULL, /* parse_brief */
691
NULL, /* parse_debrief */
693
NULL, /* comment_dot */
694
NULL, /* comment_filepos */
695
NULL, /* comment_special */
700
read_exclusion_file (file_name)
705
pop = po_alloc (&exclude_methods);
706
po_scan (pop, file_name);
712
remember_a_message (mlp, tp)
713
message_list_ty *mlp;
714
xgettext_token_ty *tp;
716
enum is_c_format is_c_format = undecided;
717
enum is_wrap do_wrap = undecided;
724
/* See whether we shall exclude this message. */
725
if (exclude != NULL && message_list_search (exclude, msgid) != NULL)
727
/* Tell the lexer to reset its comment buffer, so that the next
728
message gets the correct comments. */
729
xgettext_lex_comment_reset ();
734
/* See if we have seen this message before. */
735
mp = message_list_search (mlp, msgid);
739
is_c_format = mp->is_c_format;
740
do_wrap = mp->do_wrap;
744
static lex_pos_ty pos = { __FILE__, __LINE__ };
746
/* Allocate a new message and append the message to the list. */
747
mp = message_alloc (msgid, NULL);
748
/* Do not free msgid. */
749
message_list_append (mlp, mp);
751
/* Construct the msgstr from the prefix and suffix, otherwise use the
755
msgstr = (char *) xmalloc (strlen (msgstr_prefix)
757
+ strlen (msgstr_suffix) + 1);
758
stpcpy (stpcpy (stpcpy (msgstr, msgstr_prefix), msgid),
763
message_variant_append (mp, MESSAGE_DOMAIN_DEFAULT, msgstr,
764
strlen (msgstr) + 1, &pos);
767
/* Ask the lexer for the comments it has seen. Only do this for the
768
first instance, otherwise there could be problems; especially if
769
the same comment appears before each. */
770
if (!mp->comment_dot)
776
const char *s = xgettext_lex_comment (j);
780
/* To reduce the possibility of unwanted matches be do a two
781
step match: the line must contains `xgettext:' and one of
782
the possible format description strings. */
783
if (strstr (s, "xgettext:") != NULL)
785
is_c_format = parse_c_format_description_string (s);
786
do_wrap = parse_c_width_description_string (s);
788
/* If we found a magic string we don't print it. */
789
if (is_c_format != undecided || do_wrap != undecided)
793
|| (comment_tag != NULL && strncmp (s, comment_tag,
794
strlen (comment_tag)) == 0))
795
message_comment_dot_append (mp, s);
799
/* If not already decided, examine the msgid. */
800
if (is_c_format == undecided)
801
is_c_format = test_whether_c_format (mp->msgid);
803
mp->is_c_format = is_c_format;
804
mp->do_wrap = do_wrap == no ? no : yes; /* By default we wrap. */
806
/* Remember where we saw this msgid. */
808
message_comment_filepos (mp, tp->file_name, tp->line_number);
810
/* Tell the lexer to reset its comment buffer, so that the next
811
message gets the correct comments. */
812
xgettext_lex_comment_reset ();
819
remember_a_message_plural (mp, tp)
821
xgettext_token_ty *tp;
824
message_variant_ty *mvp;
829
msgid_plural = tp->string;
831
/* See if the message is already a plural message. */
832
if (mp->msgid_plural == NULL)
834
mp->msgid_plural = msgid_plural;
836
/* Construct the first plural form from the prefix and suffix,
837
otherwise use the empty string. The translator will have to
838
provide additional plural forms. */
839
mvp = message_variant_search (mp, MESSAGE_DOMAIN_DEFAULT);
844
msgstr1 = (char *) xmalloc (strlen (msgstr_prefix)
845
+ strlen (msgid_plural)
846
+ strlen (msgstr_suffix) + 1);
847
stpcpy (stpcpy (stpcpy (msgstr1, msgstr_prefix), msgid_plural),
852
msgstr1_len = strlen (msgstr1) + 1;
853
msgstr = (char *) xmalloc (mvp->msgstr_len + msgstr1_len);
854
memcpy (msgstr, mvp->msgstr, mvp->msgstr_len);
855
memcpy (msgstr + mvp->msgstr_len, msgstr1, msgstr1_len);
856
mvp->msgstr = msgstr;
857
mvp->msgstr_len = mvp->msgstr_len + msgstr1_len;
866
scan_c_file (filename, mlp)
867
const char *filename;
868
message_list_ty *mlp;
871
int commas_to_skip = 0; /* defined only when in states 1 and 2 */
872
int plural_commas = 0; /* defined only when in states 1 and 2 */
873
message_ty *plural_mp = NULL; /* defined only when in states 1 and 2 */
874
int paren_nesting = 0; /* defined only when in state 2 */
876
/* The file is broken into tokens. Scan the token stream, looking for
877
a keyword, followed by a left paren, followed by a string. When we
878
see this sequence, we have something to remember. We assume we are
879
looking at a valid C or C++ program, and leave the complaints about
880
the grammar to the compiler.
882
Normal handling: Look for
883
[A] keyword [B] ( ... [C] ... msgid ... ) [E]
884
Plural handling: Look for
885
[A] keyword [B] ( ... [C] ... msgid ... [D] ... msgid_plural ... ) [E]
886
At point [A]: state == 0.
887
At point [B]: state == 1, commas_to_skip set, plural_mp == NULL.
888
At point [C]: state == 2, commas_to_skip set, plural_mp == NULL.
889
At point [D]: state == 2, commas_to_skip set again, plural_mp != NULL.
890
At point [E]: state == 0. */
892
xgettext_lex_open (filename);
894
/* Start state is 0. */
899
xgettext_token_ty token;
901
/* A state machine is used to do the recognising:
902
State 0 = waiting for something to happen
903
State 1 = seen one of our keywords
904
State 2 = waiting for part of an argument */
905
xgettext_lex (&token);
908
case xgettext_token_type_keyword:
909
if (!extract_all && state == 2)
911
if (commas_to_skip == 0)
914
_("%s:%d: warning: keyword nested in keyword arg"),
915
token.file_name, token.line_number);
919
/* Here we should nest properly, but this would require a
920
potentially unbounded stack. We haven't run across an
921
example that needs this functionality yet. For now,
922
we punt and forget the outer keyword. */
924
_("%s:%d: warning: keyword between outer keyword and its arg"),
925
token.file_name, token.line_number);
927
commas_to_skip = token.argnum1 - 1;
928
plural_commas = (token.argnum2 > token.argnum1
929
? token.argnum2 - token.argnum1 : 0);
934
case xgettext_token_type_lparen:
947
case xgettext_token_type_rparen:
948
if (state == 2 && paren_nesting != 0)
954
case xgettext_token_type_comma:
955
if (state == 2 && commas_to_skip != 0)
957
if (paren_nesting == 0)
964
case xgettext_token_type_string_literal:
966
remember_a_message (mlp, &token);
967
else if (state == 2 && commas_to_skip == 0)
969
if (plural_mp == NULL)
972
if (plural_commas == 0)
973
remember_a_message (mlp, &token);
976
plural_mp = remember_a_message (mlp, &token);
977
commas_to_skip = plural_commas;
983
/* Seen an msgid_plural. */
984
remember_a_message_plural (plural_mp, &token);
996
case xgettext_token_type_symbol:
1001
case xgettext_token_type_eof:
1010
/* Close scanner. */
1011
xgettext_lex_close ();
1015
typedef struct extract_class_ty extract_class_ty;
1016
struct extract_class_ty
1018
/* Inherited instance variables and methods. */
1021
/* Cumulative list of messages. */
1022
message_list_ty *mlp;
1024
/* Cumulative comments for next message. */
1025
string_list_ty *comment;
1026
string_list_ty *comment_dot;
1029
enum is_c_format is_c_format;
1030
enum is_wrap do_wrap;
1033
lex_pos_ty *filepos;
1038
extract_constructor (that)
1041
extract_class_ty *this = (extract_class_ty *) that;
1043
this->mlp = NULL; /* actually set in read_po_file, below */
1044
this->comment = NULL;
1045
this->comment_dot = NULL;
1047
this->is_c_format = undecided;
1048
this->do_wrap = undecided;
1049
this->filepos_count = 0;
1050
this->filepos = NULL;
1055
extract_directive_domain (that, name)
1059
po_gram_error (_("this file may not contain domain directives"));
1064
extract_directive_message (that, msgid, msgid_pos, msgid_plural,
1065
msgstr, msgstr_len, msgstr_pos)
1068
lex_pos_ty *msgid_pos;
1072
lex_pos_ty *msgstr_pos;
1074
extract_class_ty *this = (extract_class_ty *)that;
1076
message_variant_ty *mvp;
1079
/* See whether we shall exclude this message. */
1080
if (exclude != NULL && message_list_search (exclude, msgid) != NULL)
1083
/* If the msgid is the empty string, it is the old header.
1084
Throw it away, we have constructed a new one. */
1090
if (this->comment != NULL)
1091
string_list_free (this->comment);
1092
if (this->comment_dot != NULL)
1093
string_list_free (this->comment_dot);
1094
if (this->filepos != NULL)
1095
free (this->filepos);
1096
this->comment = NULL;
1097
this->comment_dot = NULL;
1098
this->filepos_count = 0;
1099
this->filepos = NULL;
1101
this->is_c_format = undecided;
1102
this->do_wrap = undecided;
1106
/* See if this message ID has been seen before. */
1107
mp = message_list_search (this->mlp, msgid);
1112
mp = message_alloc (msgid, msgid_plural);
1113
message_list_append (this->mlp, mp);
1116
/* Add the accumulated comments to the message. Clear the
1117
accumulation in preparation for the next message. */
1118
if (this->comment != NULL)
1120
for (j = 0; j < this->comment->nitems; ++j)
1121
message_comment_append (mp, this->comment->item[j]);
1122
string_list_free (this->comment);
1123
this->comment = NULL;
1125
if (this->comment_dot != NULL)
1127
for (j = 0; j < this->comment_dot->nitems; ++j)
1128
message_comment_dot_append (mp, this->comment_dot->item[j]);
1129
string_list_free (this->comment_dot);
1130
this->comment_dot = NULL;
1132
mp->is_fuzzy = this->is_fuzzy;
1133
mp->is_c_format = this->is_c_format;
1134
mp->do_wrap = this->do_wrap;
1135
for (j = 0; j < this->filepos_count; ++j)
1139
pp = &this->filepos[j];
1140
message_comment_filepos (mp, pp->file_name, pp->line_number);
1141
free (pp->file_name);
1143
if (this->filepos != NULL)
1144
free (this->filepos);
1145
this->filepos_count = 0;
1146
this->filepos = NULL;
1148
this->is_c_format = undecided;
1149
this->do_wrap = undecided;
1151
/* See if this domain has been seen for this message ID. */
1152
mvp = message_variant_search (mp, MESSAGE_DOMAIN_DEFAULT);
1154
&& (msgstr_len != mvp->msgstr_len
1155
|| memcmp (msgstr, mvp->msgstr, msgstr_len) != 0))
1157
po_gram_error_at_line (msgid_pos, _("duplicate message definition"));
1158
po_gram_error_at_line (&mvp->pos, _("\
1159
...this is the location of the first definition"));
1163
message_variant_append (mp, MESSAGE_DOMAIN_DEFAULT, msgstr, msgstr_len,
1169
extract_parse_brief (that)
1172
po_lex_pass_comments (1);
1177
extract_comment (that, s)
1181
extract_class_ty *this = (extract_class_ty *) that;
1183
if (this->comment == NULL)
1184
this->comment = string_list_alloc ();
1185
string_list_append (this->comment, s);
1190
extract_comment_dot (that, s)
1194
extract_class_ty *this = (extract_class_ty *) that;
1196
if (this->comment_dot == NULL)
1197
this->comment_dot = string_list_alloc ();
1198
string_list_append (this->comment_dot, s);
1203
extract_comment_filepos (that, name, line)
1208
extract_class_ty *this = (extract_class_ty *) that;
1212
/* Write line numbers only if -n option is given. */
1213
if (line_comment != 0)
1215
nbytes = (this->filepos_count + 1) * sizeof (this->filepos[0]);
1216
this->filepos = xrealloc (this->filepos, nbytes);
1217
pp = &this->filepos[this->filepos_count++];
1218
pp->file_name = xstrdup (name);
1219
pp->line_number = line;
1225
extract_comment_special (that, s)
1229
extract_class_ty *this = (extract_class_ty *) that;
1231
if (strstr (s, "fuzzy") != NULL)
1233
this->is_c_format = parse_c_format_description_string (s);
1234
this->do_wrap = parse_c_width_description_string (s);
1238
/* So that the one parser can be used for multiple programs, and also
1239
use good data hiding and encapsulation practices, an object
1240
oriented approach has been taken. An object instance is allocated,
1241
and all actions resulting from the parse will be through
1242
invocations of method functions of that object. */
1244
static po_method_ty extract_methods =
1246
sizeof (extract_class_ty),
1247
extract_constructor,
1248
NULL, /* destructor */
1249
extract_directive_domain,
1250
extract_directive_message,
1251
extract_parse_brief,
1252
NULL, /* parse_debrief */
1254
extract_comment_dot,
1255
extract_comment_filepos,
1256
extract_comment_special
1260
/* Read the contents of the specified .po file into a message list. */
1263
read_po_file (file_name, mlp)
1264
const char *file_name;
1265
message_list_ty *mlp;
1267
po_ty *pop = po_alloc (&extract_methods);
1268
((extract_class_ty *) pop)->mlp = mlp;
1269
po_scan (pop, file_name);
1274
#define TM_YEAR_ORIGIN 1900
1276
/* Yield A - B, measured in seconds. */
1282
int ay = a->tm_year + (TM_YEAR_ORIGIN - 1);
1283
int by = b->tm_year + (TM_YEAR_ORIGIN - 1);
1284
/* Some compilers cannot handle this as a single return statement. */
1286
/* difference in day of year */
1287
a->tm_yday - b->tm_yday
1288
/* + intervening leap days */
1289
+ ((ay >> 2) - (by >> 2))
1290
- (ay / 100 - by / 100)
1291
+ ((ay / 100 >> 2) - (by / 100 >> 2))
1292
/* + difference in years * 365 */
1293
+ (long) (ay - by) * 365l);
1295
return 60l * (60l * (24l * days + (a->tm_hour - b->tm_hour))
1296
+ (a->tm_min - b->tm_min))
1297
+ (a->tm_sec - b->tm_sec);
1305
struct tm local_time;
1308
static lex_pos_ty pos = { __FILE__, __LINE__, };
1312
mp = message_alloc ("", NULL);
1315
message_comment_append (mp, "\
1316
SOME DESCRIPTIVE TITLE.\n\
1317
FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n");
1319
message_comment_append (mp, "\
1320
SOME DESCRIPTIVE TITLE.\n\
1321
Copyright (C) YEAR Free Software Foundation, Inc.\n\
1322
FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n");
1327
local_time = *localtime (&now);
1329
tz_min = difftm (&local_time, gmtime (&now)) / 60;
1336
asprintf (&msgstr, "\
1337
Project-Id-Version: PACKAGE VERSION\n\
1338
POT-Creation-Date: %d-%02d-%02d %02d:%02d%c%02ld%02ld\n\
1339
PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n\
1340
Last-Translator: FULL NAME <EMAIL@ADDRESS>\n\
1341
Language-Team: LANGUAGE <LL@li.org>\n\
1342
MIME-Version: 1.0\n\
1343
Content-Type: text/plain; charset=CHARSET\n\
1344
Content-Transfer-Encoding: 8bit\n",
1345
local_time.tm_year + TM_YEAR_ORIGIN,
1346
local_time.tm_mon + 1,
1350
tz_sign, tz_min / 60, tz_min % 60);
1353
error (EXIT_FAILURE, errno, _("while preparing output"));
1355
message_variant_append (mp, MESSAGE_DOMAIN_DEFAULT, msgstr,
1356
strlen (msgstr) + 1, &pos);
1362
/* We make a pessimistic guess whether the given string is a format
1363
string or not. Pessimistic means here that with the first
1364
occurence of an unknown format element we say `impossible'. */
1365
static enum is_c_format
1366
test_whether_c_format (s)
1369
struct printf_spec spec;
1371
if (s == NULL || *(s = find_spec (s)) == '\0')
1372
/* We return `possible' here because sometimes strings are used
1373
with printf even if they don't contain any format specifier.
1374
If the translation in this case would contain a specifier, this
1375
would result in an error. */
1378
for (s = find_spec (s); *s != '\0'; s = spec.next_fmt)
1382
(void) parse_one_spec (s, 0, &spec, &dummy);
1383
if (strchr ("iduoxXeEfgGcspnm%", spec.info.spec) == NULL)
1391
#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
1392
#define ENDOF(a) ((a) + SIZEOF(a))
1396
language_to_scanner (name)
1399
typedef struct table_ty table_ty;
1406
static table_ty table[] =
1408
{ "C", scan_c_file, },
1409
{ "C++", scan_c_file, },
1410
{ "PO", read_po_file, },
1411
/* Here will follow more languages and their scanners: awk, perl,
1412
etc... Make sure new scanners honor the --exlude-file option. */
1417
for (tp = table; tp < ENDOF(table); ++tp)
1418
if (strcasecmp (name, tp->name) == 0)
1420
error (EXIT_FAILURE, 0, _("language `%s' unknown"), name);
1427
extension_to_language (extension)
1428
const char *extension;
1430
typedef struct table_ty table_ty;
1433
const char *extension;
1434
const char *language;
1437
static table_ty table[] =
1450
/* Here will follow more file extensions: sh, pl, tcl, lisp ... */
1455
for (tp = table; tp < ENDOF(table); ++tp)
1456
if (strcmp (extension, tp->extension) == 0)
1457
return tp->language;