~ubuntu-branches/ubuntu/breezy/gettext/breezy

« back to all changes in this revision

Viewing changes to src/xgettext.c

  • Committer: Bazaar Package Importer
  • Author(s): Santiago Vila
  • Date: 2002-04-10 13:17:42 UTC
  • Revision ID: james.westby@ubuntu.com-20020410131742-npf89tsaygdolprj
Tags: upstream-0.10.40
ImportĀ upstreamĀ versionĀ 0.10.40

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.
 
4
 
 
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.
 
9
 
 
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.
 
14
 
 
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.  */
 
18
 
 
19
#ifdef HAVE_CONFIG_H
 
20
# include <config.h>
 
21
#endif
 
22
 
 
23
#include <ctype.h>
 
24
#include <errno.h>
 
25
#include <getopt.h>
 
26
#include <sys/param.h>
 
27
#include <pwd.h>
 
28
#include <stdio.h>
 
29
#include <time.h>
 
30
#include <sys/types.h>
 
31
#include <stdlib.h>
 
32
#include <locale.h>
 
33
 
 
34
#ifdef HAVE_UNISTD_H
 
35
# include <unistd.h>
 
36
#endif
 
37
 
 
38
#ifndef errno
 
39
extern int errno;
 
40
#endif
 
41
 
 
42
#include "dir-list.h"
 
43
#include "error.h"
 
44
#include "hash.h"
 
45
#include "getline.h"
 
46
#include "system.h"
 
47
#include "po.h"
 
48
#include "message.h"
 
49
#include "write-po.h"
 
50
#include "xget-lex.h"
 
51
#include "printf-parse.h"
 
52
 
 
53
#include "libgettext.h"
 
54
 
 
55
#ifndef _POSIX_VERSION
 
56
struct passwd *getpwuid ();
 
57
#endif
 
58
 
 
59
 
 
60
/* A convenience macro.  I don't like writing gettext() every time.  */
 
61
#define _(str) gettext (str)
 
62
 
 
63
 
 
64
/* If nonzero add all comments immediately preceding one of the keywords. */
 
65
static int add_all_comments;
 
66
 
 
67
/* If nonzero add comments for file name and line number for each msgid.  */
 
68
static int line_comment;
 
69
 
 
70
/* Tag used in comment of prevailing domain.  */
 
71
static char *comment_tag;
 
72
 
 
73
/* Name of default domain file.  If not set defaults to messages.po.  */
 
74
static char *default_domain;
 
75
 
 
76
/* If called with --debug option the output reflects whether format
 
77
   string recognition is done automatically or forced by the user.  */
 
78
static int do_debug;
 
79
 
 
80
/* Content of .po files with symbols to be excluded.  */
 
81
static message_list_ty *exclude;
 
82
 
 
83
/* If nonzero extract all strings.  */
 
84
static int extract_all;
 
85
 
 
86
/* Force output of PO file even if empty.  */
 
87
static int force_po;
 
88
 
 
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;
 
92
 
 
93
/* String used as prefix for msgstr.  */
 
94
static char *msgstr_prefix;
 
95
 
 
96
/* String used as suffix for msgstr.  */
 
97
static char *msgstr_suffix;
 
98
 
 
99
/* Directory in which output files are created.  */
 
100
static char *output_dir;
 
101
 
 
102
/* If nonzero omit header with information about this run.  */
 
103
static int omit_header;
 
104
 
 
105
/* String containing name the program is called with.  */
 
106
const char *program_name;
 
107
 
 
108
/* Long options.  */
 
109
static const struct option long_options[] =
 
110
{
 
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', },
 
142
  { NULL, 0, NULL, 0 }
 
143
};
 
144
 
 
145
 
 
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))
 
150
#endif
 
151
;
 
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,
 
158
                                               char *__msgstr,
 
159
                                               size_t __msgstr_len,
 
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,
 
173
                                               char *__msgstr,
 
174
                                               size_t __msgstr_len,
 
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,
 
180
                                             int __line));
 
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));
 
187
 
 
188
 
 
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 *));
 
192
 
 
193
static const char *extension_to_language PARAMS ((const char *));
 
194
static scanner_fp language_to_scanner PARAMS ((const char *));
 
195
 
 
196
 
 
197
int
 
198
main (argc, argv)
 
199
     int argc;
 
200
     char *argv[];
 
201
{
 
202
  int cnt;
 
203
  int optchar;
 
204
  int do_help = 0;
 
205
  int do_version = 0;
 
206
  message_list_ty *mlp;
 
207
  int join_existing = 0;
 
208
  int sort_output = 0;
 
209
  int sort_by_file = 0;
 
210
  char *file_name;
 
211
  const char *files_from = NULL;
 
212
  string_list_ty *file_list;
 
213
  char *output_file = NULL;
 
214
  scanner_fp scanner = NULL;
 
215
 
 
216
  /* Set program name for messages.  */
 
217
  program_name = argv[0];
 
218
  error_print_progname = error_print;
 
219
 
 
220
#ifdef HAVE_SETLOCALE
 
221
  /* Set locale via LC_ALL.  */
 
222
  setlocale (LC_ALL, "");
 
223
#endif
 
224
 
 
225
  /* Set the text message domain.  */
 
226
  bindtextdomain (PACKAGE, LOCALEDIR);
 
227
  textdomain (PACKAGE);
 
228
 
 
229
  /* Set initial value of variables.  */
 
230
  line_comment = -1;
 
231
  default_domain = MESSAGE_DOMAIN_DEFAULT;
 
232
 
 
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)
 
236
    switch (optchar)
 
237
      {
 
238
      case '\0':                /* Long option.  */
 
239
        break;
 
240
      case 'a':
 
241
        extract_all = 1;
 
242
        break;
 
243
      case 'c':
 
244
        if (optarg == NULL)
 
245
          {
 
246
            add_all_comments = 1;
 
247
            comment_tag = NULL;
 
248
          }
 
249
        else
 
250
          {
 
251
            add_all_comments = 0;
 
252
            comment_tag = optarg;
 
253
            /* We ignore leading white space.  */
 
254
            while (isspace (*comment_tag))
 
255
              ++comment_tag;
 
256
          }
 
257
        break;
 
258
      case 'C':
 
259
        scanner = language_to_scanner ("C++");
 
260
        break;
 
261
      case 'd':
 
262
        default_domain = optarg;
 
263
        break;
 
264
      case 'D':
 
265
        dir_list_append (optarg);
 
266
        break;
 
267
      case 'e':
 
268
        message_print_style_escape (0);
 
269
        break;
 
270
      case 'E':
 
271
        message_print_style_escape (1);
 
272
        break;
 
273
      case 'f':
 
274
        files_from = optarg;
 
275
        break;
 
276
      case 'F':
 
277
        sort_by_file = 1;
 
278
        break;
 
279
      case 'h':
 
280
        do_help = 1;
 
281
        break;
 
282
      case 'i':
 
283
        message_print_style_indent ();
 
284
        break;
 
285
      case 'j':
 
286
        join_existing = 1;
 
287
        break;
 
288
      case 'k':
 
289
        if (optarg == NULL || *optarg != '\0')
 
290
          xgettext_lex_keyword (optarg);
 
291
        break;
 
292
      case 'l':
 
293
        /* Accepted for backward compatibility with 0.10.35.  */
 
294
        break;
 
295
      case 'L':
 
296
        scanner = language_to_scanner (optarg);
 
297
        break;
 
298
      case 'm':
 
299
        /* -m takes an optional argument.  If none is given "" is assumed. */
 
300
        msgstr_prefix = optarg == NULL ? "" : optarg;
 
301
        break;
 
302
      case 'M':
 
303
        /* -M takes an optional argument.  If none is given "" is assumed. */
 
304
        msgstr_suffix = optarg == NULL ? "" : optarg;
 
305
        break;
 
306
      case 'n':
 
307
        line_comment = 1;
 
308
        break;
 
309
      case 'o':
 
310
        output_file = optarg;
 
311
        break;
 
312
      case 'p':
 
313
        {
 
314
          size_t len = strlen (optarg);
 
315
 
 
316
          if (output_dir != NULL)
 
317
            free (output_dir);
 
318
 
 
319
          if (optarg[len - 1] == '/')
 
320
            output_dir = xstrdup (optarg);
 
321
          else
 
322
            {
 
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"));
 
328
            }
 
329
        }
 
330
        break;
 
331
      case 's':
 
332
        sort_output = 1;
 
333
        break;
 
334
      case 'S':
 
335
        message_print_style_uniforum ();
 
336
        break;
 
337
      case 'T':
 
338
        xgettext_lex_trigraphs ();
 
339
        break;
 
340
      case 'V':
 
341
        do_version = 1;
 
342
        break;
 
343
      case 'w':
 
344
        {
 
345
          int value;
 
346
          char *endp;
 
347
          value = strtol (optarg, &endp, 10);
 
348
          if (endp != optarg)
 
349
            message_page_width_set (value);
 
350
        }
 
351
        break;
 
352
      case 'x':
 
353
        read_exclusion_file (optarg);
 
354
        break;
 
355
      default:
 
356
        usage (EXIT_FAILURE);
 
357
        /* NOTREACHED */
 
358
      }
 
359
 
 
360
  /* Normalize selected options.  */
 
361
  if (omit_header != 0 && line_comment < 0)
 
362
    line_comment = 0;
 
363
 
 
364
  if (!line_comment && sort_by_file)
 
365
    error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
 
366
           "--no-location", "--sort-by-file");
 
367
 
 
368
  if (sort_output && sort_by_file)
 
369
    error (EXIT_FAILURE, 0, _("%s and %s are mutually exclusive"),
 
370
           "--sort-output", "--sort-by-file");
 
371
 
 
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"));
 
375
 
 
376
  if (!xgettext_any_keywords ())
 
377
    {
 
378
      error (0, 0, _("\
 
379
xgettext cannot work without keywords to look for"));
 
380
      usage (EXIT_FAILURE);
 
381
    }
 
382
 
 
383
  /* Version information requested.  */
 
384
  if (do_version)
 
385
    {
 
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\
 
391
"),
 
392
              "1995-1998, 2000, 2001");
 
393
      printf (_("Written by %s.\n"), "Ulrich Drepper");
 
394
      exit (EXIT_SUCCESS);
 
395
    }
 
396
 
 
397
  /* Help is requested.  */
 
398
  if (do_help)
 
399
    usage (EXIT_SUCCESS);
 
400
 
 
401
  /* Test whether we have some input files given.  */
 
402
  if (files_from == NULL && optind >= argc)
 
403
    {
 
404
      error (EXIT_SUCCESS, 0, _("no input file given"));
 
405
      usage (EXIT_FAILURE);
 
406
    }
 
407
 
 
408
  /* Canonize msgstr prefix/suffix.  */
 
409
  if (msgstr_prefix != NULL && msgstr_suffix == NULL)
 
410
    msgstr_suffix = "";
 
411
  else if (msgstr_prefix == NULL && msgstr_suffix != NULL)
 
412
    msgstr_prefix = NULL;
 
413
 
 
414
  /* Default output directory is the current directory.  */
 
415
  if (output_dir == NULL)
 
416
    output_dir = ".";
 
417
 
 
418
  /* Construct the name of the output file.  If the default domain has
 
419
     the special name "-" we write to stdout.  */
 
420
  if (output_file)
 
421
    {
 
422
      if (IS_ABSOLUTE_PATH (output_file) || strcmp (output_file, "-") == 0)
 
423
        file_name = xstrdup (output_file);
 
424
      else
 
425
        /* Please do NOT add a .po suffix! */
 
426
        file_name = concatenated_pathname (output_dir, output_file, NULL);
 
427
    }
 
428
  else if (strcmp (default_domain, "-") == 0)
 
429
    file_name = "-";
 
430
  else
 
431
    file_name = concatenated_pathname (output_dir, default_domain, ".po");
 
432
 
 
433
  /* Determine list of files we have to process.  */
 
434
  if (files_from != NULL)
 
435
    file_list = read_name_from_file (files_from);
 
436
  else
 
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]);
 
441
 
 
442
  /* Allocate a message list to remember all the messages.  */
 
443
  mlp = message_list_alloc ();
 
444
 
 
445
  /* Generate a header, so that we know how and when this PO file was
 
446
     created.  */
 
447
  if (!omit_header)
 
448
      message_list_append (mlp, construct_header ());
 
449
 
 
450
  /* Read in the old messages, so that we can add to them.  */
 
451
  if (join_existing)
 
452
    read_po_file (file_name, mlp);
 
453
 
 
454
  /* Process all input files.  */
 
455
  for (cnt = 0; cnt < file_list->nitems; ++cnt)
 
456
    {
 
457
      const char *fname;
 
458
      scanner_fp scan_file;
 
459
 
 
460
      fname = file_list->item[cnt];
 
461
 
 
462
      if (scanner)
 
463
        scan_file = scanner;
 
464
      else
 
465
        {
 
466
          const char *extension;
 
467
          const char *language;
 
468
 
 
469
          /* Work out what the file extension is.  */
 
470
          extension = strrchr (fname, '/');
 
471
          if (!extension)
 
472
            extension = fname;
 
473
          extension = strrchr (extension, '.');
 
474
          if (extension)
 
475
            ++extension;
 
476
          else
 
477
            extension = "";
 
478
 
 
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)
 
483
          {
 
484
            error (0, 0, _("\
 
485
warning: file `%s' extension `%s' is unknown; will try C"), fname, extension);
 
486
            language = "C";
 
487
          }
 
488
          scan_file = language_to_scanner (language);
 
489
        }
 
490
 
 
491
      /* Scan the file.  */
 
492
      scan_file (fname, mlp);
 
493
    }
 
494
  string_list_free (file_list);
 
495
 
 
496
  /* Sorting the list of messages.  */
 
497
  if (sort_by_file)
 
498
    message_list_sort_by_filepos (mlp);
 
499
  else if (sort_output)
 
500
    message_list_sort_by_msgid (mlp);
 
501
 
 
502
  /* Write the PO file.  */
 
503
  message_list_print (mlp, file_name, force_po, do_debug);
 
504
 
 
505
  exit (EXIT_SUCCESS);
 
506
}
 
507
 
 
508
 
 
509
/* Display usage information and exit.  */
 
510
static void
 
511
usage (status)
 
512
     int status;
 
513
{
 
514
  if (status != EXIT_SUCCESS)
 
515
    fprintf (stderr, _("Try `%s --help' for more information.\n"),
 
516
             program_name);
 
517
  else
 
518
    {
 
519
      /* xgettext: no-wrap */
 
520
      printf (_("\
 
521
Usage: %s [OPTION] INPUTFILE ...\n\
 
522
Extract translatable string from given input files.\n\
 
523
\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"),
 
538
              program_name);
 
539
      /* xgettext: no-wrap */
 
540
      printf (_("\
 
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 */
 
552
      fputs (_("\
 
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\
 
563
\n\
 
564
If INPUTFILE is -, standard input is read.\n"), stdout);
 
565
      fputs (_("Report bugs to <bug-gnu-utils@gnu.org>.\n"),
 
566
             stdout);
 
567
    }
 
568
 
 
569
  exit (status);
 
570
}
 
571
 
 
572
 
 
573
/* The address of this function will be assigned to the hook in the error
 
574
   functions.  */
 
575
static void
 
576
error_print ()
 
577
{
 
578
  /* We don't want the program name to be printed in messages.  */
 
579
}
 
580
 
 
581
 
 
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;
 
586
{
 
587
  size_t line_len = 0;
 
588
  char *line_buf = NULL;
 
589
  FILE *fp;
 
590
  string_list_ty *result;
 
591
 
 
592
  if (strcmp (file_name, "-") == 0)
 
593
    fp = stdin;
 
594
  else
 
595
    {
 
596
      fp = fopen (file_name, "r");
 
597
      if (fp == NULL)
 
598
        error (EXIT_FAILURE, errno,
 
599
               _("error while opening \"%s\" for reading"), file_name);
 
600
    }
 
601
 
 
602
  result = string_list_alloc ();
 
603
 
 
604
  while (!feof (fp))
 
605
    {
 
606
      /* Read next line from file.  */
 
607
      int len = getline (&line_buf, &line_len, fp);
 
608
 
 
609
      /* In case of an error leave loop.  */
 
610
      if (len < 0)
 
611
        break;
 
612
 
 
613
      /* Remove trailing '\n'.  */
 
614
      if (len > 0 && line_buf[len - 1] == '\n')
 
615
        line_buf[--len] = '\0';
 
616
 
 
617
      /* Test if we have to ignore the line.  */
 
618
      if (*line_buf == '\0' || *line_buf == '#')
 
619
        continue;
 
620
 
 
621
      string_list_append_unique (result, line_buf);
 
622
    }
 
623
 
 
624
  /* Free buffer allocated through getline.  */
 
625
  if (line_buf != NULL)
 
626
    free (line_buf);
 
627
 
 
628
  /* Close input stream.  */
 
629
  if (fp != stdin)
 
630
    fclose (fp);
 
631
 
 
632
  return result;
 
633
}
 
634
 
 
635
 
 
636
static void
 
637
exclude_directive_domain (pop, name)
 
638
     po_ty *pop;
 
639
     char *name;
 
640
{
 
641
  po_gram_error (_("this file may not contain domain directives"));
 
642
}
 
643
 
 
644
 
 
645
static void
 
646
exclude_directive_message (pop, msgid, msgid_pos, msgid_plural,
 
647
                           msgstr, msgstr_len, msgstr_pos)
 
648
     po_ty *pop;
 
649
     char *msgid;
 
650
     lex_pos_ty *msgid_pos;
 
651
     char *msgid_plural;
 
652
     char *msgstr;
 
653
     size_t msgstr_len;
 
654
     lex_pos_ty *msgstr_pos;
 
655
{
 
656
  message_ty *mp;
 
657
 
 
658
  /* See if this message ID has been seen before.  */
 
659
  if (exclude == NULL)
 
660
    exclude = message_list_alloc ();
 
661
  mp = message_list_search (exclude, msgid);
 
662
  if (mp != NULL)
 
663
    free (msgid);
 
664
  else
 
665
    {
 
666
      mp = message_alloc (msgid, msgid_plural);
 
667
      /* Do not free msgid.  */
 
668
      message_list_append (exclude, mp);
 
669
    }
 
670
 
 
671
  /* All we care about is the msgid.  Throw the msgstr away.
 
672
     Don't even check for duplicate msgids.  */
 
673
  free (msgstr);
 
674
}
 
675
 
 
676
 
 
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.  */
 
682
 
 
683
static po_method_ty exclude_methods =
 
684
{
 
685
  sizeof (po_ty),
 
686
  NULL, /* constructor */
 
687
  NULL, /* destructor */
 
688
  exclude_directive_domain,
 
689
  exclude_directive_message,
 
690
  NULL, /* parse_brief */
 
691
  NULL, /* parse_debrief */
 
692
  NULL, /* comment */
 
693
  NULL, /* comment_dot */
 
694
  NULL, /* comment_filepos */
 
695
  NULL, /* comment_special */
 
696
};
 
697
 
 
698
 
 
699
static void
 
700
read_exclusion_file (file_name)
 
701
     char *file_name;
 
702
{
 
703
  po_ty *pop;
 
704
 
 
705
  pop = po_alloc (&exclude_methods);
 
706
  po_scan (pop, file_name);
 
707
  po_free (pop);
 
708
}
 
709
 
 
710
 
 
711
static message_ty *
 
712
remember_a_message (mlp, tp)
 
713
     message_list_ty *mlp;
 
714
     xgettext_token_ty *tp;
 
715
{
 
716
  enum is_c_format is_c_format = undecided;
 
717
  enum is_wrap do_wrap = undecided;
 
718
  char *msgid;
 
719
  message_ty *mp;
 
720
  char *msgstr;
 
721
 
 
722
  msgid = tp->string;
 
723
 
 
724
  /* See whether we shall exclude this message.  */
 
725
  if (exclude != NULL && message_list_search (exclude, msgid) != NULL)
 
726
    {
 
727
      /* Tell the lexer to reset its comment buffer, so that the next
 
728
         message gets the correct comments.  */
 
729
      xgettext_lex_comment_reset ();
 
730
 
 
731
      return NULL;
 
732
    }
 
733
 
 
734
  /* See if we have seen this message before.  */
 
735
  mp = message_list_search (mlp, msgid);
 
736
  if (mp != NULL)
 
737
    {
 
738
      free (msgid);
 
739
      is_c_format = mp->is_c_format;
 
740
      do_wrap = mp->do_wrap;
 
741
    }
 
742
  else
 
743
    {
 
744
      static lex_pos_ty pos = { __FILE__, __LINE__ };
 
745
 
 
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);
 
750
 
 
751
      /* Construct the msgstr from the prefix and suffix, otherwise use the
 
752
         empty string.  */
 
753
      if (msgstr_prefix)
 
754
        {
 
755
          msgstr = (char *) xmalloc (strlen (msgstr_prefix)
 
756
                                     + strlen (msgid)
 
757
                                     + strlen (msgstr_suffix) + 1);
 
758
          stpcpy (stpcpy (stpcpy (msgstr, msgstr_prefix), msgid),
 
759
                  msgstr_suffix);
 
760
        }
 
761
      else
 
762
        msgstr = "";
 
763
      message_variant_append (mp, MESSAGE_DOMAIN_DEFAULT, msgstr,
 
764
                              strlen (msgstr) + 1, &pos);
 
765
    }
 
766
 
 
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)
 
771
    {
 
772
      int j;
 
773
 
 
774
      for (j = 0; ; ++j)
 
775
        {
 
776
          const char *s = xgettext_lex_comment (j);
 
777
          if (s == NULL)
 
778
            break;
 
779
 
 
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)
 
784
            {
 
785
              is_c_format = parse_c_format_description_string (s);
 
786
              do_wrap = parse_c_width_description_string (s);
 
787
 
 
788
              /* If we found a magic string we don't print it.  */
 
789
              if (is_c_format != undecided || do_wrap != undecided)
 
790
                continue;
 
791
            }
 
792
          if (add_all_comments
 
793
              || (comment_tag != NULL && strncmp (s, comment_tag,
 
794
                                                  strlen (comment_tag)) == 0))
 
795
            message_comment_dot_append (mp, s);
 
796
        }
 
797
    }
 
798
 
 
799
  /* If not already decided, examine the msgid.  */
 
800
  if (is_c_format == undecided)
 
801
    is_c_format = test_whether_c_format (mp->msgid);
 
802
 
 
803
  mp->is_c_format = is_c_format;
 
804
  mp->do_wrap = do_wrap == no ? no : yes;       /* By default we wrap.  */
 
805
 
 
806
  /* Remember where we saw this msgid.  */
 
807
  if (line_comment)
 
808
    message_comment_filepos (mp, tp->file_name, tp->line_number);
 
809
 
 
810
  /* Tell the lexer to reset its comment buffer, so that the next
 
811
     message gets the correct comments.  */
 
812
  xgettext_lex_comment_reset ();
 
813
 
 
814
  return mp;
 
815
}
 
816
 
 
817
 
 
818
static void
 
819
remember_a_message_plural (mp, tp)
 
820
     message_ty *mp;
 
821
     xgettext_token_ty *tp;
 
822
{
 
823
  char *msgid_plural;
 
824
  message_variant_ty *mvp;
 
825
  char *msgstr1;
 
826
  size_t msgstr1_len;
 
827
  char *msgstr;
 
828
 
 
829
  msgid_plural = tp->string;
 
830
 
 
831
  /* See if the message is already a plural message.  */
 
832
  if (mp->msgid_plural == NULL)
 
833
    {
 
834
      mp->msgid_plural = msgid_plural;
 
835
 
 
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);
 
840
      if (mvp != NULL)
 
841
        {
 
842
          if (msgstr_prefix)
 
843
            {
 
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),
 
848
                      msgstr_suffix);
 
849
            }
 
850
          else
 
851
            msgstr1 = "";
 
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;
 
858
        }
 
859
    }
 
860
  else
 
861
    free (msgid_plural);
 
862
}
 
863
 
 
864
 
 
865
static void
 
866
scan_c_file (filename, mlp)
 
867
     const char *filename;
 
868
     message_list_ty *mlp;
 
869
{
 
870
  int state;
 
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 */
 
875
 
 
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.
 
881
 
 
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.  */
 
891
 
 
892
  xgettext_lex_open (filename);
 
893
 
 
894
  /* Start state is 0.  */
 
895
  state = 0;
 
896
 
 
897
  while (1)
 
898
   {
 
899
     xgettext_token_ty token;
 
900
 
 
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);
 
906
     switch (token.type)
 
907
       {
 
908
       case xgettext_token_type_keyword:
 
909
         if (!extract_all && state == 2)
 
910
           {
 
911
             if (commas_to_skip == 0)
 
912
               {
 
913
                 error (0, 0,
 
914
                        _("%s:%d: warning: keyword nested in keyword arg"),
 
915
                        token.file_name, token.line_number);
 
916
                 continue;
 
917
               }
 
918
 
 
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.  */
 
923
             error (0, 0,
 
924
                    _("%s:%d: warning: keyword between outer keyword and its arg"),
 
925
                    token.file_name, token.line_number);
 
926
           }
 
927
         commas_to_skip = token.argnum1 - 1;
 
928
         plural_commas = (token.argnum2 > token.argnum1
 
929
                          ? token.argnum2 - token.argnum1 : 0);
 
930
         plural_mp = NULL;
 
931
         state = 1;
 
932
         continue;
 
933
 
 
934
       case xgettext_token_type_lparen:
 
935
         switch (state)
 
936
           {
 
937
           case 1:
 
938
             paren_nesting = 0;
 
939
             state = 2;
 
940
             break;
 
941
           case 2:
 
942
             paren_nesting++;
 
943
             break;
 
944
           }
 
945
         continue;
 
946
 
 
947
       case xgettext_token_type_rparen:
 
948
         if (state == 2 && paren_nesting != 0)
 
949
           paren_nesting--;
 
950
         else
 
951
           state = 0;
 
952
         continue;
 
953
 
 
954
       case xgettext_token_type_comma:
 
955
         if (state == 2 && commas_to_skip != 0)
 
956
           {
 
957
             if (paren_nesting == 0)
 
958
               commas_to_skip--;
 
959
           }
 
960
         else
 
961
           state = 0;
 
962
         continue;
 
963
 
 
964
       case xgettext_token_type_string_literal:
 
965
         if (extract_all)
 
966
           remember_a_message (mlp, &token);
 
967
         else if (state == 2 && commas_to_skip == 0)
 
968
           {
 
969
             if (plural_mp == NULL)
 
970
               {
 
971
                 /* Seen an msgid.  */
 
972
                 if (plural_commas == 0)
 
973
                   remember_a_message (mlp, &token);
 
974
                 else
 
975
                   {
 
976
                     plural_mp = remember_a_message (mlp, &token);
 
977
                     commas_to_skip = plural_commas;
 
978
                     plural_commas = 0;
 
979
                   }
 
980
               }
 
981
             else
 
982
               {
 
983
                 /* Seen an msgid_plural.  */
 
984
                 remember_a_message_plural (plural_mp, &token);
 
985
                 plural_mp = NULL;
 
986
               }
 
987
           }
 
988
         else
 
989
           {
 
990
             free (token.string);
 
991
             if (state == 1)
 
992
               state = 0;
 
993
           }
 
994
         continue;
 
995
 
 
996
       case xgettext_token_type_symbol:
 
997
         if (state == 1)
 
998
           state = 0;
 
999
         continue;
 
1000
 
 
1001
       case xgettext_token_type_eof:
 
1002
         break;
 
1003
 
 
1004
       default:
 
1005
         abort ();
 
1006
       }
 
1007
     break;
 
1008
   }
 
1009
 
 
1010
  /* Close scanner.  */
 
1011
  xgettext_lex_close ();
 
1012
}
 
1013
 
 
1014
 
 
1015
typedef struct extract_class_ty extract_class_ty;
 
1016
struct extract_class_ty
 
1017
{
 
1018
  /* Inherited instance variables and methods.  */
 
1019
  PO_BASE_TY
 
1020
 
 
1021
  /* Cumulative list of messages.  */
 
1022
  message_list_ty *mlp;
 
1023
 
 
1024
  /* Cumulative comments for next message.  */
 
1025
  string_list_ty *comment;
 
1026
  string_list_ty *comment_dot;
 
1027
 
 
1028
  int is_fuzzy;
 
1029
  enum is_c_format is_c_format;
 
1030
  enum is_wrap do_wrap;
 
1031
 
 
1032
  int filepos_count;
 
1033
  lex_pos_ty *filepos;
 
1034
};
 
1035
 
 
1036
 
 
1037
static void
 
1038
extract_constructor (that)
 
1039
     po_ty *that;
 
1040
{
 
1041
  extract_class_ty *this = (extract_class_ty *) that;
 
1042
 
 
1043
  this->mlp = NULL; /* actually set in read_po_file, below */
 
1044
  this->comment = NULL;
 
1045
  this->comment_dot = NULL;
 
1046
  this->is_fuzzy = 0;
 
1047
  this->is_c_format = undecided;
 
1048
  this->do_wrap = undecided;
 
1049
  this->filepos_count = 0;
 
1050
  this->filepos = NULL;
 
1051
}
 
1052
 
 
1053
 
 
1054
static void
 
1055
extract_directive_domain (that, name)
 
1056
     po_ty *that;
 
1057
     char *name;
 
1058
{
 
1059
  po_gram_error (_("this file may not contain domain directives"));
 
1060
}
 
1061
 
 
1062
 
 
1063
static void
 
1064
extract_directive_message (that, msgid, msgid_pos, msgid_plural,
 
1065
                           msgstr, msgstr_len, msgstr_pos)
 
1066
     po_ty *that;
 
1067
     char *msgid;
 
1068
     lex_pos_ty *msgid_pos;
 
1069
     char *msgid_plural;
 
1070
     char *msgstr;
 
1071
     size_t msgstr_len;
 
1072
     lex_pos_ty *msgstr_pos;
 
1073
{
 
1074
  extract_class_ty *this = (extract_class_ty *)that;
 
1075
  message_ty *mp;
 
1076
  message_variant_ty *mvp;
 
1077
  size_t j;
 
1078
 
 
1079
  /* See whether we shall exclude this message.  */
 
1080
  if (exclude != NULL && message_list_search (exclude, msgid) != NULL)
 
1081
    goto discard;
 
1082
 
 
1083
  /* If the msgid is the empty string, it is the old header.
 
1084
     Throw it away, we have constructed a new one.  */
 
1085
  if (*msgid == '\0')
 
1086
    {
 
1087
      discard:
 
1088
      free (msgid);
 
1089
      free (msgstr);
 
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;
 
1100
      this->is_fuzzy = 0;
 
1101
      this->is_c_format = undecided;
 
1102
      this->do_wrap = undecided;
 
1103
      return;
 
1104
    }
 
1105
 
 
1106
  /* See if this message ID has been seen before.  */
 
1107
  mp = message_list_search (this->mlp, msgid);
 
1108
  if (mp)
 
1109
    free (msgid);
 
1110
  else
 
1111
    {
 
1112
      mp = message_alloc (msgid, msgid_plural);
 
1113
      message_list_append (this->mlp, mp);
 
1114
    }
 
1115
 
 
1116
  /* Add the accumulated comments to the message.  Clear the
 
1117
     accumulation in preparation for the next message. */
 
1118
  if (this->comment != NULL)
 
1119
    {
 
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;
 
1124
    }
 
1125
  if (this->comment_dot != NULL)
 
1126
    {
 
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;
 
1131
    }
 
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)
 
1136
    {
 
1137
      lex_pos_ty *pp;
 
1138
 
 
1139
      pp = &this->filepos[j];
 
1140
      message_comment_filepos (mp, pp->file_name, pp->line_number);
 
1141
      free (pp->file_name);
 
1142
    }
 
1143
  if (this->filepos != NULL)
 
1144
    free (this->filepos);
 
1145
  this->filepos_count = 0;
 
1146
  this->filepos = NULL;
 
1147
  this->is_fuzzy = 0;
 
1148
  this->is_c_format = undecided;
 
1149
  this->do_wrap = undecided;
 
1150
 
 
1151
  /* See if this domain has been seen for this message ID.  */
 
1152
  mvp = message_variant_search (mp, MESSAGE_DOMAIN_DEFAULT);
 
1153
  if (mvp != NULL
 
1154
      && (msgstr_len != mvp->msgstr_len
 
1155
          || memcmp (msgstr, mvp->msgstr, msgstr_len) != 0))
 
1156
    {
 
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"));
 
1160
      free (msgstr);
 
1161
    }
 
1162
  else
 
1163
    message_variant_append (mp, MESSAGE_DOMAIN_DEFAULT, msgstr, msgstr_len,
 
1164
                            msgstr_pos);
 
1165
}
 
1166
 
 
1167
 
 
1168
static void
 
1169
extract_parse_brief (that)
 
1170
     po_ty *that;
 
1171
{
 
1172
  po_lex_pass_comments (1);
 
1173
}
 
1174
 
 
1175
 
 
1176
static void
 
1177
extract_comment (that, s)
 
1178
     po_ty *that;
 
1179
     const char *s;
 
1180
{
 
1181
  extract_class_ty *this = (extract_class_ty *) that;
 
1182
 
 
1183
  if (this->comment == NULL)
 
1184
    this->comment = string_list_alloc ();
 
1185
  string_list_append (this->comment, s);
 
1186
}
 
1187
 
 
1188
 
 
1189
static void
 
1190
extract_comment_dot (that, s)
 
1191
     po_ty *that;
 
1192
     const char *s;
 
1193
{
 
1194
  extract_class_ty *this = (extract_class_ty *) that;
 
1195
 
 
1196
  if (this->comment_dot == NULL)
 
1197
    this->comment_dot = string_list_alloc ();
 
1198
  string_list_append (this->comment_dot, s);
 
1199
}
 
1200
 
 
1201
 
 
1202
static void
 
1203
extract_comment_filepos (that, name, line)
 
1204
     po_ty *that;
 
1205
     const char *name;
 
1206
     int line;
 
1207
{
 
1208
  extract_class_ty *this = (extract_class_ty *) that;
 
1209
  size_t nbytes;
 
1210
  lex_pos_ty *pp;
 
1211
 
 
1212
  /* Write line numbers only if -n option is given.  */
 
1213
  if (line_comment != 0)
 
1214
    {
 
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;
 
1220
    }
 
1221
}
 
1222
 
 
1223
 
 
1224
static void
 
1225
extract_comment_special (that, s)
 
1226
     po_ty *that;
 
1227
     const char *s;
 
1228
{
 
1229
  extract_class_ty *this = (extract_class_ty *) that;
 
1230
 
 
1231
  if (strstr (s, "fuzzy") != NULL)
 
1232
    this->is_fuzzy = 1;
 
1233
  this->is_c_format = parse_c_format_description_string (s);
 
1234
  this->do_wrap = parse_c_width_description_string (s);
 
1235
}
 
1236
 
 
1237
 
 
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.  */
 
1243
 
 
1244
static po_method_ty extract_methods =
 
1245
{
 
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 */
 
1253
  extract_comment,
 
1254
  extract_comment_dot,
 
1255
  extract_comment_filepos,
 
1256
  extract_comment_special
 
1257
};
 
1258
 
 
1259
 
 
1260
/* Read the contents of the specified .po file into a message list.  */
 
1261
 
 
1262
static void
 
1263
read_po_file (file_name, mlp)
 
1264
     const char *file_name;
 
1265
     message_list_ty *mlp;
 
1266
{
 
1267
  po_ty *pop = po_alloc (&extract_methods);
 
1268
  ((extract_class_ty *) pop)->mlp = mlp;
 
1269
  po_scan (pop, file_name);
 
1270
  po_free (pop);
 
1271
}
 
1272
 
 
1273
 
 
1274
#define TM_YEAR_ORIGIN 1900
 
1275
 
 
1276
/* Yield A - B, measured in seconds.  */
 
1277
static long
 
1278
difftm (a, b)
 
1279
     const struct tm *a;
 
1280
     const struct tm *b;
 
1281
{
 
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.  */
 
1285
  long days = (
 
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);
 
1294
 
 
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);
 
1298
}
 
1299
 
 
1300
 
 
1301
static message_ty *
 
1302
construct_header ()
 
1303
{
 
1304
  time_t now;
 
1305
  struct tm local_time;
 
1306
  message_ty *mp;
 
1307
  char *msgstr;
 
1308
  static lex_pos_ty pos = { __FILE__, __LINE__, };
 
1309
  char tz_sign;
 
1310
  long tz_min;
 
1311
 
 
1312
  mp = message_alloc ("", NULL);
 
1313
 
 
1314
  if (foreign_user)
 
1315
    message_comment_append (mp, "\
 
1316
SOME DESCRIPTIVE TITLE.\n\
 
1317
FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n");
 
1318
  else
 
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");
 
1323
 
 
1324
  mp->is_fuzzy = 1;
 
1325
 
 
1326
  time (&now);
 
1327
  local_time = *localtime (&now);
 
1328
  tz_sign = '+';
 
1329
  tz_min = difftm (&local_time, gmtime (&now)) / 60;
 
1330
  if (tz_min < 0)
 
1331
    {
 
1332
      tz_min = -tz_min;
 
1333
      tz_sign = '-';
 
1334
    }
 
1335
 
 
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,
 
1347
            local_time.tm_mday,
 
1348
            local_time.tm_hour,
 
1349
            local_time.tm_min,
 
1350
            tz_sign, tz_min / 60, tz_min % 60);
 
1351
 
 
1352
  if (msgstr == NULL)
 
1353
    error (EXIT_FAILURE, errno, _("while preparing output"));
 
1354
 
 
1355
  message_variant_append (mp, MESSAGE_DOMAIN_DEFAULT, msgstr,
 
1356
                          strlen (msgstr) + 1, &pos);
 
1357
 
 
1358
  return mp;
 
1359
}
 
1360
 
 
1361
 
 
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)
 
1367
     const char *s;
 
1368
{
 
1369
  struct printf_spec spec;
 
1370
 
 
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.  */
 
1376
    return impossible;
 
1377
 
 
1378
  for (s = find_spec (s); *s != '\0'; s = spec.next_fmt)
 
1379
    {
 
1380
      size_t dummy;
 
1381
 
 
1382
      (void) parse_one_spec (s, 0, &spec, &dummy);
 
1383
      if (strchr ("iduoxXeEfgGcspnm%", spec.info.spec) == NULL)
 
1384
        return impossible;
 
1385
    }
 
1386
 
 
1387
  return possible;
 
1388
}
 
1389
 
 
1390
 
 
1391
#define SIZEOF(a) (sizeof(a) / sizeof(a[0]))
 
1392
#define ENDOF(a) ((a) + SIZEOF(a))
 
1393
 
 
1394
 
 
1395
static scanner_fp
 
1396
language_to_scanner (name)
 
1397
  const char *name;
 
1398
{
 
1399
  typedef struct table_ty table_ty;
 
1400
  struct table_ty
 
1401
  {
 
1402
    const char *name;
 
1403
    scanner_fp func;
 
1404
  };
 
1405
 
 
1406
  static table_ty table[] =
 
1407
  {
 
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.  */
 
1413
  };
 
1414
 
 
1415
  table_ty *tp;
 
1416
 
 
1417
  for (tp = table; tp < ENDOF(table); ++tp)
 
1418
    if (strcasecmp (name, tp->name) == 0)
 
1419
      return tp->func;
 
1420
  error (EXIT_FAILURE, 0, _("language `%s' unknown"), name);
 
1421
  /* NOTREACHED */
 
1422
  return NULL;
 
1423
}
 
1424
 
 
1425
 
 
1426
static const char *
 
1427
extension_to_language (extension)
 
1428
  const char *extension;
 
1429
{
 
1430
  typedef struct table_ty table_ty;
 
1431
  struct table_ty
 
1432
  {
 
1433
    const char *extension;
 
1434
    const char *language;
 
1435
  };
 
1436
 
 
1437
  static table_ty table[] =
 
1438
  {
 
1439
    { "c",      "C",    },
 
1440
    { "C",      "C++",  },
 
1441
    { "c++",    "C++",  },
 
1442
    { "cc",     "C++",  },
 
1443
    { "cxx",    "C++",  },
 
1444
    { "cpp",    "C++",  },
 
1445
    { "h",      "C",    },
 
1446
    { "hpp",    "C++",  },
 
1447
    { "po",     "PO",   },
 
1448
    { "pot",    "PO",   },
 
1449
    { "pox",    "PO",   },
 
1450
    /* Here will follow more file extensions: sh, pl, tcl, lisp ... */
 
1451
  };
 
1452
 
 
1453
  table_ty *tp;
 
1454
 
 
1455
  for (tp = table; tp < ENDOF(table); ++tp)
 
1456
    if (strcmp (extension, tp->extension) == 0)
 
1457
      return tp->language;
 
1458
  return NULL;
 
1459
}