Imported Upstream version 0.18.3.2
[platform/upstream/gettext.git] / gettext-tools / src / read-catalog-abstract.c
1 /* Reading PO files, abstract class.
2    Copyright (C) 1995-1996, 1998, 2000-2009 Free Software Foundation, Inc.
3
4    This file was written by Peter Miller <millerp@canb.auug.org.au>
5
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15
16    You should have received a copy of the GNU General Public License
17    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
18
19
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 /* Specification.  */
25 #include "read-catalog-abstract.h"
26
27 #include <limits.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include "xalloc.h"
32 #include "xvasprintf.h"
33 #include "po-xerror.h"
34 #include "error.h"
35 #include "gettext.h"
36
37 /* Local variables.  */
38 static abstract_catalog_reader_ty *callback_arg;
39
40
41 /* ========================================================================= */
42 /* Allocating and freeing instances of abstract_catalog_reader_ty.  */
43
44
45 abstract_catalog_reader_ty *
46 catalog_reader_alloc (abstract_catalog_reader_class_ty *method_table)
47 {
48   abstract_catalog_reader_ty *pop;
49
50   pop = (abstract_catalog_reader_ty *) xmalloc (method_table->size);
51   pop->methods = method_table;
52   if (method_table->constructor)
53     method_table->constructor (pop);
54   return pop;
55 }
56
57
58 void
59 catalog_reader_free (abstract_catalog_reader_ty *pop)
60 {
61   if (pop->methods->destructor)
62     pop->methods->destructor (pop);
63   free (pop);
64 }
65
66
67 /* ========================================================================= */
68 /* Inline functions to invoke the methods.  */
69
70
71 static inline void
72 call_parse_brief (abstract_catalog_reader_ty *pop)
73 {
74   if (pop->methods->parse_brief)
75     pop->methods->parse_brief (pop);
76 }
77
78 static inline void
79 call_parse_debrief (abstract_catalog_reader_ty *pop)
80 {
81   if (pop->methods->parse_debrief)
82     pop->methods->parse_debrief (pop);
83 }
84
85 static inline void
86 call_directive_domain (abstract_catalog_reader_ty *pop, char *name)
87 {
88   if (pop->methods->directive_domain)
89     pop->methods->directive_domain (pop, name);
90 }
91
92 static inline void
93 call_directive_message (abstract_catalog_reader_ty *pop,
94                         char *msgctxt,
95                         char *msgid,
96                         lex_pos_ty *msgid_pos,
97                         char *msgid_plural,
98                         char *msgstr, size_t msgstr_len,
99                         lex_pos_ty *msgstr_pos,
100                         char *prev_msgctxt,
101                         char *prev_msgid,
102                         char *prev_msgid_plural,
103                         bool force_fuzzy, bool obsolete)
104 {
105   if (pop->methods->directive_message)
106     pop->methods->directive_message (pop, msgctxt,
107                                      msgid, msgid_pos, msgid_plural,
108                                      msgstr, msgstr_len, msgstr_pos,
109                                      prev_msgctxt,
110                                      prev_msgid,
111                                      prev_msgid_plural,
112                                      force_fuzzy, obsolete);
113 }
114
115 static inline void
116 call_comment (abstract_catalog_reader_ty *pop, const char *s)
117 {
118   if (pop->methods->comment != NULL)
119     pop->methods->comment (pop, s);
120 }
121
122 static inline void
123 call_comment_dot (abstract_catalog_reader_ty *pop, const char *s)
124 {
125   if (pop->methods->comment_dot != NULL)
126     pop->methods->comment_dot (pop, s);
127 }
128
129 static inline void
130 call_comment_filepos (abstract_catalog_reader_ty *pop, const char *name,
131                       size_t line)
132 {
133   if (pop->methods->comment_filepos)
134     pop->methods->comment_filepos (pop, name, line);
135 }
136
137 static inline void
138 call_comment_special (abstract_catalog_reader_ty *pop, const char *s)
139 {
140   if (pop->methods->comment_special != NULL)
141     pop->methods->comment_special (pop, s);
142 }
143
144
145 /* ========================================================================= */
146 /* Exported functions.  */
147
148
149 static inline void
150 parse_start (abstract_catalog_reader_ty *pop)
151 {
152   /* The parse will call the po_callback_... functions (see below)
153      when the various directive are recognised.  The callback_arg
154      variable is used to tell these functions which instance is to
155      have the relevant method invoked.  */
156   callback_arg = pop;
157
158   call_parse_brief (pop);
159 }
160
161 static inline void
162 parse_end (abstract_catalog_reader_ty *pop)
163 {
164   call_parse_debrief (pop);
165   callback_arg = NULL;
166 }
167
168
169 void
170 catalog_reader_parse (abstract_catalog_reader_ty *pop, FILE *fp,
171                       const char *real_filename, const char *logical_filename,
172                       catalog_input_format_ty input_syntax)
173 {
174   /* Parse the stream's content.  */
175   parse_start (pop);
176   input_syntax->parse (pop, fp, real_filename, logical_filename);
177   parse_end (pop);
178
179   if (error_message_count > 0)
180     po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
181                /*real_filename*/ NULL, (size_t)(-1), (size_t)(-1), false,
182                xasprintf (ngettext ("found %d fatal error",
183                                     "found %d fatal errors",
184                                     error_message_count),
185                           error_message_count));
186   error_message_count = 0;
187 }
188
189
190 /* ========================================================================= */
191 /* Callbacks used by po-gram.y or po-lex.c, indirectly from
192    catalog_reader_parse.  */
193
194
195 /* This function is called by po_gram_lex() whenever a domain directive
196    has been seen.  */
197 void
198 po_callback_domain (char *name)
199 {
200   /* assert(callback_arg); */
201   call_directive_domain (callback_arg, name);
202 }
203
204
205 /* This function is called by po_gram_lex() whenever a message has been
206    seen.  */
207 void
208 po_callback_message (char *msgctxt,
209                      char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
210                      char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos,
211                      char *prev_msgctxt,
212                      char *prev_msgid,
213                      char *prev_msgid_plural,
214                      bool force_fuzzy, bool obsolete)
215 {
216   /* assert(callback_arg); */
217   call_directive_message (callback_arg, msgctxt,
218                           msgid, msgid_pos, msgid_plural,
219                           msgstr, msgstr_len, msgstr_pos,
220                           prev_msgctxt, prev_msgid, prev_msgid_plural,
221                           force_fuzzy, obsolete);
222 }
223
224
225 void
226 po_callback_comment (const char *s)
227 {
228   /* assert(callback_arg); */
229   call_comment (callback_arg, s);
230 }
231
232
233 void
234 po_callback_comment_dot (const char *s)
235 {
236   /* assert(callback_arg); */
237   call_comment_dot (callback_arg, s);
238 }
239
240
241 /* This function is called by po_parse_comment_filepos(), once for each
242    filename.  */
243 void
244 po_callback_comment_filepos (const char *name, size_t line)
245 {
246   /* assert(callback_arg); */
247   call_comment_filepos (callback_arg, name, line);
248 }
249
250
251 void
252 po_callback_comment_special (const char *s)
253 {
254   /* assert(callback_arg); */
255   call_comment_special (callback_arg, s);
256 }
257
258
259 /* Parse a special comment and put the result in *fuzzyp, formatp, *rangep,
260    *wrapp.  */
261 void
262 po_parse_comment_special (const char *s,
263                           bool *fuzzyp, enum is_format formatp[NFORMATS],
264                           struct argument_range *rangep, enum is_wrap *wrapp)
265 {
266   size_t i;
267
268   *fuzzyp = false;
269   for (i = 0; i < NFORMATS; i++)
270     formatp[i] = undecided;
271   rangep->min = -1;
272   rangep->max = -1;
273   *wrapp = undecided;
274
275   while (*s != '\0')
276     {
277       const char *t;
278
279       /* Skip whitespace.  */
280       while (*s != '\0' && strchr ("\n \t\r\f\v,", *s) != NULL)
281         s++;
282
283       /* Collect a token.  */
284       t = s;
285       while (*s != '\0' && strchr ("\n \t\r\f\v,", *s) == NULL)
286         s++;
287       if (s != t)
288         {
289           size_t len = s - t;
290
291           /* Accept fuzzy flag.  */
292           if (len == 5 && memcmp (t, "fuzzy", 5) == 0)
293             {
294               *fuzzyp = true;
295               continue;
296             }
297
298           /* Accept format description.  */
299           if (len >= 7 && memcmp (t + len - 7, "-format", 7) == 0)
300             {
301               const char *p;
302               size_t n;
303               enum is_format value;
304
305               p = t;
306               n = len - 7;
307
308               if (n >= 3 && memcmp (p, "no-", 3) == 0)
309                 {
310                   p += 3;
311                   n -= 3;
312                   value = no;
313                 }
314               else if (n >= 9 && memcmp (p, "possible-", 9) == 0)
315                 {
316                   p += 9;
317                   n -= 9;
318                   value = possible;
319                 }
320               else if (n >= 11 && memcmp (p, "impossible-", 11) == 0)
321                 {
322                   p += 11;
323                   n -= 11;
324                   value = impossible;
325                 }
326               else
327                 value = yes;
328
329               for (i = 0; i < NFORMATS; i++)
330                 if (strlen (format_language[i]) == n
331                     && memcmp (format_language[i], p, n) == 0)
332                   {
333                     formatp[i] = value;
334                     break;
335                   }
336               if (i < NFORMATS)
337                 continue;
338             }
339
340           /* Accept range description "range: <min>..<max>".  */
341           if (len == 6 && memcmp (t, "range:", 6) == 0)
342             {
343               /* Skip whitespace.  */
344               while (*s != '\0' && strchr ("\n \t\r\f\v,", *s) != NULL)
345                 s++;
346
347               /* Collect a token.  */
348               t = s;
349               while (*s != '\0' && strchr ("\n \t\r\f\v,", *s) == NULL)
350                 s++;
351               /* Parse it.  */
352               if (*t >= '0' && *t <= '9')
353                 {
354                   unsigned int min = 0;
355
356                   for (; *t >= '0' && *t <= '9'; t++)
357                     {
358                       if (min <= INT_MAX / 10)
359                         {
360                           min = 10 * min + (*t - '0');
361                           if (min > INT_MAX)
362                             min = INT_MAX;
363                         }
364                       else
365                         /* Avoid integer overflow.  */
366                         min = INT_MAX;
367                     }
368                   if (*t++ == '.')
369                     if (*t++ == '.')
370                       if (*t >= '0' && *t <= '9')
371                         {
372                           unsigned int max = 0;
373                           for (; *t >= '0' && *t <= '9'; t++)
374                             {
375                               if (max <= INT_MAX / 10)
376                                 {
377                                   max = 10 * max + (*t - '0');
378                                   if (max > INT_MAX)
379                                     max = INT_MAX;
380                                 }
381                               else
382                                 /* Avoid integer overflow.  */
383                                 max = INT_MAX;
384                             }
385                           if (min <= max)
386                             {
387                               rangep->min = min;
388                               rangep->max = max;
389                               continue;
390                             }
391                         }
392                 }
393             }
394
395           /* Accept wrap description.  */
396           if (len == 4 && memcmp (t, "wrap", 4) == 0)
397             {
398               *wrapp = yes;
399               continue;
400             }
401           if (len == 7 && memcmp (t, "no-wrap", 7) == 0)
402             {
403               *wrapp = no;
404               continue;
405             }
406
407           /* Unknown special comment marker.  It may have been generated
408              from a future xgettext version.  Ignore it.  */
409         }
410     }
411 }
412
413
414 /* Parse a GNU style file comment.
415    Syntax: an arbitrary number of
416              STRING COLON NUMBER
417            or
418              STRING
419    The latter style, without line number, occurs in PO files converted e.g.
420    from Pascal .rst files or from OpenOffice resource files.
421    Call po_callback_comment_filepos for each of them.  */
422 static void
423 po_parse_comment_filepos (const char *s)
424 {
425   while (*s != '\0')
426     {
427       while (*s == ' ' || *s == '\t' || *s == '\n')
428         s++;
429       if (*s != '\0')
430         {
431           const char *string_start = s;
432
433           do
434             s++;
435           while (!(*s == '\0' || *s == ' ' || *s == '\t' || *s == '\n'));
436
437           /* See if there is a COLON and NUMBER after the STRING, separated
438              through optional spaces.  */
439           {
440             const char *p = s;
441
442             while (*p == ' ' || *p == '\t' || *p == '\n')
443               p++;
444
445             if (*p == ':')
446               {
447                 p++;
448
449                 while (*p == ' ' || *p == '\t' || *p == '\n')
450                   p++;
451
452                 if (*p >= '0' && *p <= '9')
453                   {
454                     /* Accumulate a number.  */
455                     size_t n = 0;
456
457                     do
458                       {
459                         n = n * 10 + (*p - '0');
460                         p++;
461                       }
462                     while (*p >= '0' && *p <= '9');
463
464                     if (*p == '\0' || *p == ' ' || *p == '\t' || *p == '\n')
465                       {
466                         /* Parsed a GNU style file comment with spaces.  */
467                         const char *string_end = s;
468                         size_t string_length = string_end - string_start;
469                         char *string = XNMALLOC (string_length + 1, char);
470
471                         memcpy (string, string_start, string_length);
472                         string[string_length] = '\0';
473
474                         po_callback_comment_filepos (string, n);
475
476                         free (string);
477
478                         s = p;
479                         continue;
480                       }
481                   }
482               }
483           }
484
485           /* See if there is a COLON at the end of STRING and a NUMBER after
486              it, separated through optional spaces.  */
487           if (s[-1] == ':')
488             {
489               const char *p = s;
490
491               while (*p == ' ' || *p == '\t' || *p == '\n')
492                 p++;
493
494               if (*p >= '0' && *p <= '9')
495                 {
496                   /* Accumulate a number.  */
497                   size_t n = 0;
498
499                   do
500                     {
501                       n = n * 10 + (*p - '0');
502                       p++;
503                     }
504                   while (*p >= '0' && *p <= '9');
505
506                   if (*p == '\0' || *p == ' ' || *p == '\t' || *p == '\n')
507                     {
508                       /* Parsed a GNU style file comment with spaces.  */
509                       const char *string_end = s - 1;
510                       size_t string_length = string_end - string_start;
511                       char *string = XNMALLOC (string_length + 1, char);
512
513                       memcpy (string, string_start, string_length);
514                       string[string_length] = '\0';
515
516                       po_callback_comment_filepos (string, n);
517
518                       free (string);
519
520                       s = p;
521                       continue;
522                     }
523                 }
524             }
525
526           /* See if there is a COLON and NUMBER at the end of the STRING,
527              without separating spaces.  */
528           {
529             const char *p = s;
530
531             while (p > string_start)
532               {
533                 p--;
534                 if (!(*p >= '0' && *p <= '9'))
535                   {
536                     p++;
537                     break;
538                   }
539               }
540
541             /* p now points to the beginning of the trailing digits segment
542                at the end of STRING.  */
543
544             if (p < s
545                 && p > string_start + 1
546                 && p[-1] == ':')
547               {
548                 /* Parsed a GNU style file comment without spaces.  */
549                 const char *string_end = p - 1;
550
551                 /* Accumulate a number.  */
552                 {
553                   size_t n = 0;
554
555                   do
556                     {
557                       n = n * 10 + (*p - '0');
558                       p++;
559                     }
560                   while (p < s);
561
562                   {
563                     size_t string_length = string_end - string_start;
564                     char *string = XNMALLOC (string_length + 1, char);
565
566                     memcpy (string, string_start, string_length);
567                     string[string_length] = '\0';
568
569                     po_callback_comment_filepos (string, n);
570
571                     free (string);
572
573                     continue;
574                   }
575                 }
576               }
577           }
578
579           /* Parsed a file comment without line number.  */
580           {
581             const char *string_end = s;
582             size_t string_length = string_end - string_start;
583             char *string = XNMALLOC (string_length + 1, char);
584
585             memcpy (string, string_start, string_length);
586             string[string_length] = '\0';
587
588             po_callback_comment_filepos (string, (size_t)(-1));
589
590             free (string);
591           }
592         }
593     }
594 }
595
596
597 /* Parse a SunOS or Solaris style file comment.
598    Syntax of SunOS style:
599      FILE_KEYWORD COLON STRING COMMA LINE_KEYWORD COLON NUMBER
600    Syntax of Solaris style:
601      FILE_KEYWORD COLON STRING COMMA LINE_KEYWORD NUMBER_KEYWORD COLON NUMBER
602    where
603      FILE_KEYWORD ::= "file" | "File"
604      COLON ::= ":"
605      COMMA ::= ","
606      LINE_KEYWORD ::= "line"
607      NUMBER_KEYWORD ::= "number"
608      NUMBER ::= [0-9]+
609    Return true if parsed, false if not a comment of this form. */
610 static bool
611 po_parse_comment_solaris_filepos (const char *s)
612 {
613   if (s[0] == ' '
614       && (s[1] == 'F' || s[1] == 'f')
615       && s[2] == 'i' && s[3] == 'l' && s[4] == 'e'
616       && s[5] == ':')
617     {
618       const char *string_start;
619       const char *string_end;
620
621       {
622         const char *p = s + 6;
623
624         while (*p == ' ' || *p == '\t')
625           p++;
626         string_start = p;
627       }
628
629       for (string_end = string_start; *string_end != '\0'; string_end++)
630         {
631           const char *p = string_end;
632
633           while (*p == ' ' || *p == '\t')
634             p++;
635
636           if (*p == ',')
637             {
638               p++;
639
640               while (*p == ' ' || *p == '\t')
641                 p++;
642
643               if (p[0] == 'l' && p[1] == 'i' && p[2] == 'n' && p[3] == 'e')
644                 {
645                   p += 4;
646
647                   while (*p == ' ' || *p == '\t')
648                     p++;
649
650                   if (p[0] == 'n' && p[1] == 'u' && p[2] == 'm'
651                       && p[3] == 'b' && p[4] == 'e' && p[5] == 'r')
652                     {
653                       p += 6;
654                       while (*p == ' ' || *p == '\t')
655                         p++;
656                     }
657
658                   if (*p == ':')
659                     {
660                       p++;
661
662                       if (*p >= '0' && *p <= '9')
663                         {
664                           /* Accumulate a number.  */
665                           size_t n = 0;
666
667                           do
668                             {
669                               n = n * 10 + (*p - '0');
670                               p++;
671                             }
672                           while (*p >= '0' && *p <= '9');
673
674                           while (*p == ' ' || *p == '\t' || *p == '\n')
675                             p++;
676
677                           if (*p == '\0')
678                             {
679                               /* Parsed a Sun style file comment.  */
680                               size_t string_length = string_end - string_start;
681                               char *string =
682                                 XNMALLOC (string_length + 1, char);
683
684                               memcpy (string, string_start, string_length);
685                               string[string_length] = '\0';
686
687                               po_callback_comment_filepos (string, n);
688
689                               free (string);
690                               return true;
691                             }
692                         }
693                     }
694                 }
695             }
696         }
697     }
698
699   return false;
700 }
701
702
703 /* This function is called by po_gram_lex() whenever a comment is
704    seen.  It analyzes the comment to see what sort it is, and then
705    dispatches it to the appropriate method: call_comment, call_comment_dot,
706    call_comment_filepos (via po_parse_comment_filepos), or
707    call_comment_special.  */
708 void
709 po_callback_comment_dispatcher (const char *s)
710 {
711   if (*s == '.')
712     {
713       s++;
714       /* There is usually a space before the comment.  People don't
715          consider it part of the comment, therefore remove it here.  */
716       if (*s == ' ')
717         s++;
718       po_callback_comment_dot (s);
719     }
720   else if (*s == ':')
721     {
722       /* Parse the file location string.  The appropriate callback will be
723          invoked.  */
724       po_parse_comment_filepos (s + 1);
725     }
726   else if (*s == ',' || *s == '!')
727     {
728       /* Get all entries in the special comment line.  */
729       po_callback_comment_special (s + 1);
730     }
731   else
732     {
733       /* It looks like a plain vanilla comment, but Solaris-style file
734          position lines do, too.  Try to parse the lot.  If the parse
735          succeeds, the appropriate callback will be invoked.  */
736       if (po_parse_comment_solaris_filepos (s))
737         /* Do nothing, it is a Sun-style file pos line.  */ ;
738       else
739         {
740           /* There is usually a space before the comment.  People don't
741              consider it part of the comment, therefore remove it here.  */
742           if (*s == ' ')
743             s++;
744           po_callback_comment (s);
745         }
746     }
747 }