Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / read-catalog.c
1 /* Reading PO files.
2    Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2015 Free
3    Software Foundation, Inc.
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 #ifdef HAVE_CONFIG_H
20 # include <config.h>
21 #endif
22
23 /* Specification.  */
24 #include "read-catalog.h"
25
26 #include <stdbool.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include "open-catalog.h"
31 #include "po-charset.h"
32 #include "po-xerror.h"
33 #include "xalloc.h"
34 #include "gettext.h"
35
36 #define _(str) gettext (str)
37
38
39 /* ========================================================================= */
40 /* Inline functions to invoke the methods.  */
41
42 static inline void
43 call_set_domain (struct default_catalog_reader_ty *this, char *name)
44 {
45   default_catalog_reader_class_ty *methods =
46     (default_catalog_reader_class_ty *) this->methods;
47
48   if (methods->set_domain)
49     methods->set_domain (this, name);
50 }
51
52 static inline void
53 call_add_message (struct default_catalog_reader_ty *this,
54                   char *msgctxt,
55                   char *msgid, lex_pos_ty *msgid_pos, char *msgid_plural,
56                   char *msgstr, size_t msgstr_len, lex_pos_ty *msgstr_pos,
57                   char *prev_msgctxt, char *prev_msgid, char *prev_msgid_plural,
58                   bool force_fuzzy, bool obsolete)
59 {
60   default_catalog_reader_class_ty *methods =
61     (default_catalog_reader_class_ty *) this->methods;
62
63   if (methods->add_message)
64     methods->add_message (this, msgctxt,
65                           msgid, msgid_pos, msgid_plural,
66                           msgstr, msgstr_len, msgstr_pos,
67                           prev_msgctxt, prev_msgid, prev_msgid_plural,
68                           force_fuzzy, obsolete);
69 }
70
71 static inline void
72 call_frob_new_message (struct default_catalog_reader_ty *this, message_ty *mp,
73                        const lex_pos_ty *msgid_pos,
74                        const lex_pos_ty *msgstr_pos)
75 {
76   default_catalog_reader_class_ty *methods =
77     (default_catalog_reader_class_ty *) this->methods;
78
79   if (methods->frob_new_message)
80     methods->frob_new_message (this, mp, msgid_pos, msgstr_pos);
81 }
82
83
84 /* ========================================================================= */
85 /* Implementation of default_catalog_reader_ty's methods.  */
86
87
88 /* Implementation of methods declared in the superclass.  */
89
90
91 /* Prepare for first message.  */
92 void
93 default_constructor (abstract_catalog_reader_ty *that)
94 {
95   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
96   size_t i;
97
98   this->domain = MESSAGE_DOMAIN_DEFAULT;
99   this->comment = NULL;
100   this->comment_dot = NULL;
101   this->filepos_count = 0;
102   this->filepos = NULL;
103   this->is_fuzzy = false;
104   for (i = 0; i < NFORMATS; i++)
105     this->is_format[i] = undecided;
106   this->range.min = -1;
107   this->range.max = -1;
108   this->do_wrap = undecided;
109   for (i = 0; i < NSYNTAXCHECKS; i++)
110     this->do_syntax_check[i] = undecided;
111 }
112
113
114 void
115 default_destructor (abstract_catalog_reader_ty *that)
116 {
117   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
118   size_t j;
119
120   /* Do not free this->mdlp and this->mlp.  */
121   if (this->handle_comments)
122     {
123       if (this->comment != NULL)
124         string_list_free (this->comment);
125       if (this->comment_dot != NULL)
126         string_list_free (this->comment_dot);
127     }
128
129   for (j = 0; j < this->filepos_count; ++j)
130     free (this->filepos[j].file_name);
131   if (this->filepos != NULL)
132     free (this->filepos);
133 }
134
135
136 void
137 default_parse_brief (abstract_catalog_reader_ty *that)
138 {
139   /* We need to parse comments, because even if this->handle_comments
140      is false, we need to know which messages are fuzzy.  */
141   po_lex_pass_comments (true);
142 }
143
144
145 void
146 default_parse_debrief (abstract_catalog_reader_ty *that)
147 {
148 }
149
150
151 /* Add the accumulated comments to the message.  */
152 static void
153 default_copy_comment_state (default_catalog_reader_ty *this, message_ty *mp)
154 {
155   size_t j, i;
156
157   if (this->handle_comments)
158     {
159       if (this->comment != NULL)
160         for (j = 0; j < this->comment->nitems; ++j)
161           message_comment_append (mp, this->comment->item[j]);
162       if (this->comment_dot != NULL)
163         for (j = 0; j < this->comment_dot->nitems; ++j)
164           message_comment_dot_append (mp, this->comment_dot->item[j]);
165     }
166   for (j = 0; j < this->filepos_count; ++j)
167     {
168       lex_pos_ty *pp;
169
170       pp = &this->filepos[j];
171       message_comment_filepos (mp, pp->file_name, pp->line_number);
172     }
173   mp->is_fuzzy = this->is_fuzzy;
174   for (i = 0; i < NFORMATS; i++)
175     mp->is_format[i] = this->is_format[i];
176   mp->range = this->range;
177   mp->do_wrap = this->do_wrap;
178   for (i = 0; i < NSYNTAXCHECKS; i++)
179     mp->do_syntax_check[i] = this->do_syntax_check[i];
180 }
181
182
183 static void
184 default_reset_comment_state (default_catalog_reader_ty *this)
185 {
186   size_t j, i;
187
188   if (this->handle_comments)
189     {
190       if (this->comment != NULL)
191         {
192           string_list_free (this->comment);
193           this->comment = NULL;
194         }
195       if (this->comment_dot != NULL)
196         {
197           string_list_free (this->comment_dot);
198           this->comment_dot = NULL;
199         }
200     }
201   for (j = 0; j < this->filepos_count; ++j)
202     free (this->filepos[j].file_name);
203   if (this->filepos != NULL)
204     free (this->filepos);
205   this->filepos_count = 0;
206   this->filepos = NULL;
207   this->is_fuzzy = false;
208   for (i = 0; i < NFORMATS; i++)
209     this->is_format[i] = undecided;
210   this->range.min = -1;
211   this->range.max = -1;
212   this->do_wrap = undecided;
213   for (i = 0; i < NSYNTAXCHECKS; i++)
214     this->do_syntax_check[i] = undecided;
215 }
216
217
218 /* Process 'domain' directive from .po file.  */
219 void
220 default_directive_domain (abstract_catalog_reader_ty *that, char *name)
221 {
222   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
223
224   call_set_domain (this, name);
225
226   /* If there are accumulated comments, throw them away, they are
227      probably part of the file header, or about the domain directive,
228      and will be unrelated to the next message.  */
229   default_reset_comment_state (this);
230 }
231
232
233 /* Process ['msgctxt'/]'msgid'/'msgstr' pair from .po file.  */
234 void
235 default_directive_message (abstract_catalog_reader_ty *that,
236                            char *msgctxt,
237                            char *msgid,
238                            lex_pos_ty *msgid_pos,
239                            char *msgid_plural,
240                            char *msgstr, size_t msgstr_len,
241                            lex_pos_ty *msgstr_pos,
242                            char *prev_msgctxt,
243                            char *prev_msgid, char *prev_msgid_plural,
244                            bool force_fuzzy, bool obsolete)
245 {
246   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
247
248   call_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural,
249                     msgstr, msgstr_len, msgstr_pos,
250                     prev_msgctxt, prev_msgid, prev_msgid_plural,
251                     force_fuzzy, obsolete);
252
253   /* Prepare for next message.  */
254   default_reset_comment_state (this);
255 }
256
257
258 void
259 default_comment (abstract_catalog_reader_ty *that, const char *s)
260 {
261   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
262
263   if (this->handle_comments)
264     {
265       if (this->comment == NULL)
266         this->comment = string_list_alloc ();
267       string_list_append (this->comment, s);
268     }
269 }
270
271
272 void
273 default_comment_dot (abstract_catalog_reader_ty *that, const char *s)
274 {
275   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
276
277   if (this->handle_comments)
278     {
279       if (this->comment_dot == NULL)
280         this->comment_dot = string_list_alloc ();
281       string_list_append (this->comment_dot, s);
282     }
283 }
284
285
286 void
287 default_comment_filepos (abstract_catalog_reader_ty *that,
288                          const char *name, size_t line)
289 {
290   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
291   size_t nbytes;
292   lex_pos_ty *pp;
293
294   nbytes = (this->filepos_count + 1) * sizeof (this->filepos[0]);
295   this->filepos = xrealloc (this->filepos, nbytes);
296   pp = &this->filepos[this->filepos_count++];
297   pp->file_name = xstrdup (name);
298   pp->line_number = line;
299 }
300
301
302 /* Test for '#, fuzzy' comments and warn.  */
303 void
304 default_comment_special (abstract_catalog_reader_ty *that, const char *s)
305 {
306   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
307
308   po_parse_comment_special (s, &this->is_fuzzy, this->is_format, &this->range,
309                             &this->do_wrap, this->do_syntax_check);
310 }
311
312
313 /* Default implementation of methods not inherited from the superclass.  */
314
315
316 void
317 default_set_domain (default_catalog_reader_ty *this, char *name)
318 {
319   if (this->allow_domain_directives)
320     /* Override current domain name.  Don't free memory.  */
321     this->domain = name;
322   else
323     {
324       po_gram_error_at_line (&gram_pos,
325                              _("this file may not contain domain directives"));
326
327       /* NAME was allocated in po-gram-gen.y but is not used anywhere.  */
328       free (name);
329     }
330 }
331
332 void
333 default_add_message (default_catalog_reader_ty *this,
334                      char *msgctxt,
335                      char *msgid,
336                      lex_pos_ty *msgid_pos,
337                      char *msgid_plural,
338                      char *msgstr, size_t msgstr_len,
339                      lex_pos_ty *msgstr_pos,
340                      char *prev_msgctxt,
341                      char *prev_msgid,
342                      char *prev_msgid_plural,
343                      bool force_fuzzy, bool obsolete)
344 {
345   message_ty *mp;
346
347   if (this->mdlp != NULL)
348     /* Select the appropriate sublist of this->mdlp.  */
349     this->mlp = msgdomain_list_sublist (this->mdlp, this->domain, true);
350
351   if (this->allow_duplicates && msgid[0] != '\0')
352     /* Doesn't matter if this message ID has been seen before.  */
353     mp = NULL;
354   else
355     /* See if this message ID has been seen before.  */
356     mp = message_list_search (this->mlp, msgctxt, msgid);
357
358   if (mp)
359     {
360       if (!(this->allow_duplicates_if_same_msgstr
361             && msgstr_len == mp->msgstr_len
362             && memcmp (msgstr, mp->msgstr, msgstr_len) == 0))
363         {
364           /* We give a fatal error about this, regardless whether the
365              translations are equal or different.  This is for consistency
366              with msgmerge, msgcat and others.  The user can use the
367              msguniq program to get rid of duplicates.  */
368           po_xerror2 (PO_SEVERITY_ERROR,
369                       NULL, msgid_pos->file_name, msgid_pos->line_number,
370                       (size_t)(-1), false, _("duplicate message definition"),
371                       mp, NULL, 0, 0, false,
372                       _("this is the location of the first definition"));
373         }
374       /* We don't need the just constructed entries' parameter string
375          (allocated in po-gram-gen.y).  */
376       free (msgid);
377       if (msgid_plural != NULL)
378         free (msgid_plural);
379       free (msgstr);
380       if (msgctxt != NULL)
381         free (msgctxt);
382       if (prev_msgctxt != NULL)
383         free (prev_msgctxt);
384       if (prev_msgid != NULL)
385         free (prev_msgid);
386       if (prev_msgid_plural != NULL)
387         free (prev_msgid_plural);
388
389       /* Add the accumulated comments to the message.  */
390       default_copy_comment_state (this, mp);
391     }
392   else
393     {
394       /* Construct message to add to the list.
395          Obsolete message go into the list at least for duplicate checking.
396          It's the caller's responsibility to ignore obsolete messages when
397          appropriate.  */
398       mp = message_alloc (msgctxt, msgid, msgid_plural, msgstr, msgstr_len,
399                           msgstr_pos);
400       mp->prev_msgctxt = prev_msgctxt;
401       mp->prev_msgid = prev_msgid;
402       mp->prev_msgid_plural = prev_msgid_plural;
403       mp->obsolete = obsolete;
404       default_copy_comment_state (this, mp);
405       if (force_fuzzy)
406         mp->is_fuzzy = true;
407
408       call_frob_new_message (this, mp, msgid_pos, msgstr_pos);
409
410       message_list_append (this->mlp, mp);
411     }
412 }
413
414
415 /* So that the one parser can be used for multiple programs, and also
416    use good data hiding and encapsulation practices, an object
417    oriented approach has been taken.  An object instance is allocated,
418    and all actions resulting from the parse will be through
419    invocations of method functions of that object.  */
420
421 static default_catalog_reader_class_ty default_methods =
422 {
423   {
424     sizeof (default_catalog_reader_ty),
425     default_constructor,
426     default_destructor,
427     default_parse_brief,
428     default_parse_debrief,
429     default_directive_domain,
430     default_directive_message,
431     default_comment,
432     default_comment_dot,
433     default_comment_filepos,
434     default_comment_special
435   },
436   default_set_domain, /* set_domain */
437   default_add_message, /* add_message */
438   NULL /* frob_new_message */
439 };
440
441
442 default_catalog_reader_ty *
443 default_catalog_reader_alloc (default_catalog_reader_class_ty *method_table)
444 {
445   return
446     (default_catalog_reader_ty *) catalog_reader_alloc (&method_table->super);
447 }
448
449
450 /* ========================================================================= */
451 /* Exported functions.  */
452
453
454 /* If false, duplicate msgids in the same domain and file generate an error.
455    If true, such msgids are allowed; the caller should treat them
456    appropriately.  Defaults to false.  */
457 bool allow_duplicates = false;
458
459
460 msgdomain_list_ty *
461 read_catalog_stream (FILE *fp, const char *real_filename,
462                      const char *logical_filename,
463                      catalog_input_format_ty input_syntax)
464 {
465   default_catalog_reader_ty *pop;
466   msgdomain_list_ty *mdlp;
467
468   pop = default_catalog_reader_alloc (&default_methods);
469   pop->handle_comments = true;
470   pop->allow_domain_directives = true;
471   pop->allow_duplicates = allow_duplicates;
472   pop->allow_duplicates_if_same_msgstr = false;
473   pop->file_name = real_filename;
474   pop->mdlp = msgdomain_list_alloc (!pop->allow_duplicates);
475   pop->mlp = msgdomain_list_sublist (pop->mdlp, pop->domain, true);
476   if (input_syntax->produces_utf8)
477     /* We know a priori that input_syntax->parse convert strings to UTF-8.  */
478     pop->mdlp->encoding = po_charset_utf8;
479   po_lex_pass_obsolete_entries (true);
480   catalog_reader_parse ((abstract_catalog_reader_ty *) pop, fp, real_filename,
481                         logical_filename, input_syntax);
482   mdlp = pop->mdlp;
483   catalog_reader_free ((abstract_catalog_reader_ty *) pop);
484   return mdlp;
485 }
486
487
488 msgdomain_list_ty *
489 read_catalog_file (const char *filename, catalog_input_format_ty input_syntax)
490 {
491   char *real_filename;
492   FILE *fp = open_catalog_file (filename, &real_filename, true);
493   msgdomain_list_ty *result;
494
495   result = read_catalog_stream (fp, real_filename, filename, input_syntax);
496
497   if (fp != stdin)
498     fclose (fp);
499
500   return result;
501 }