1 /* Reading PO files.
2    Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014-2016 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 <https://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
call_set_domain(struct default_catalog_reader_ty * this,char * name)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
call_add_message(struct default_catalog_reader_ty * this,char * msgctxt,char * msgid,lex_pos_ty * msgid_pos,char * msgid_plural,char * msgstr,size_t msgstr_len,lex_pos_ty * msgstr_pos,char * prev_msgctxt,char * prev_msgid,char * prev_msgid_plural,bool force_fuzzy,bool obsolete)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
call_frob_new_message(struct default_catalog_reader_ty * this,message_ty * mp,const lex_pos_ty * msgid_pos,const lex_pos_ty * msgstr_pos)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
default_constructor(abstract_catalog_reader_ty * that)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
default_destructor(abstract_catalog_reader_ty * that)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
default_parse_brief(abstract_catalog_reader_ty * that)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
default_parse_debrief(abstract_catalog_reader_ty * that)146 default_parse_debrief (abstract_catalog_reader_ty *that)
147 {
148 }
149 
150 
151 /* Add the accumulated comments to the message.  */
152 static void
default_copy_comment_state(default_catalog_reader_ty * this,message_ty * mp)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
default_reset_comment_state(default_catalog_reader_ty * this)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
default_directive_domain(abstract_catalog_reader_ty * that,char * name)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
default_directive_message(abstract_catalog_reader_ty * that,char * msgctxt,char * msgid,lex_pos_ty * msgid_pos,char * msgid_plural,char * msgstr,size_t msgstr_len,lex_pos_ty * msgstr_pos,char * prev_msgctxt,char * prev_msgid,char * prev_msgid_plural,bool force_fuzzy,bool obsolete)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
default_comment(abstract_catalog_reader_ty * that,const char * s)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
default_comment_dot(abstract_catalog_reader_ty * that,const char * s)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
default_comment_filepos(abstract_catalog_reader_ty * that,const char * name,size_t line)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
default_comment_special(abstract_catalog_reader_ty * that,const char * s)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
default_set_domain(default_catalog_reader_ty * this,char * name)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
default_add_message(default_catalog_reader_ty * this,char * msgctxt,char * msgid,lex_pos_ty * msgid_pos,char * msgid_plural,char * msgstr,size_t msgstr_len,lex_pos_ty * msgstr_pos,char * prev_msgctxt,char * prev_msgid,char * prev_msgid_plural,bool force_fuzzy,bool obsolete)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       if (msgid_plural != NULL)
401         free (msgid_plural);
402       mp->prev_msgctxt = prev_msgctxt;
403       mp->prev_msgid = prev_msgid;
404       mp->prev_msgid_plural = prev_msgid_plural;
405       mp->obsolete = obsolete;
406       default_copy_comment_state (this, mp);
407       if (force_fuzzy)
408         mp->is_fuzzy = true;
409 
410       call_frob_new_message (this, mp, msgid_pos, msgstr_pos);
411 
412       message_list_append (this->mlp, mp);
413     }
414 }
415 
416 
417 /* So that the one parser can be used for multiple programs, and also
418    use good data hiding and encapsulation practices, an object
419    oriented approach has been taken.  An object instance is allocated,
420    and all actions resulting from the parse will be through
421    invocations of method functions of that object.  */
422 
423 static default_catalog_reader_class_ty default_methods =
424 {
425   {
426     sizeof (default_catalog_reader_ty),
427     default_constructor,
428     default_destructor,
429     default_parse_brief,
430     default_parse_debrief,
431     default_directive_domain,
432     default_directive_message,
433     default_comment,
434     default_comment_dot,
435     default_comment_filepos,
436     default_comment_special
437   },
438   default_set_domain, /* set_domain */
439   default_add_message, /* add_message */
440   NULL /* frob_new_message */
441 };
442 
443 
444 default_catalog_reader_ty *
default_catalog_reader_alloc(default_catalog_reader_class_ty * method_table)445 default_catalog_reader_alloc (default_catalog_reader_class_ty *method_table)
446 {
447   return
448     (default_catalog_reader_ty *) catalog_reader_alloc (&method_table->super);
449 }
450 
451 
452 /* ========================================================================= */
453 /* Exported functions.  */
454 
455 
456 /* If false, duplicate msgids in the same domain and file generate an error.
457    If true, such msgids are allowed; the caller should treat them
458    appropriately.  Defaults to false.  */
459 bool allow_duplicates = false;
460 
461 
462 msgdomain_list_ty *
read_catalog_stream(FILE * fp,const char * real_filename,const char * logical_filename,catalog_input_format_ty input_syntax)463 read_catalog_stream (FILE *fp, const char *real_filename,
464                      const char *logical_filename,
465                      catalog_input_format_ty input_syntax)
466 {
467   default_catalog_reader_ty *pop;
468   msgdomain_list_ty *mdlp;
469 
470   pop = default_catalog_reader_alloc (&default_methods);
471   pop->handle_comments = true;
472   pop->allow_domain_directives = true;
473   pop->allow_duplicates = allow_duplicates;
474   pop->allow_duplicates_if_same_msgstr = false;
475   pop->file_name = real_filename;
476   pop->mdlp = msgdomain_list_alloc (!pop->allow_duplicates);
477   pop->mlp = msgdomain_list_sublist (pop->mdlp, pop->domain, true);
478   if (input_syntax->produces_utf8)
479     /* We know a priori that input_syntax->parse convert strings to UTF-8.  */
480     pop->mdlp->encoding = po_charset_utf8;
481   po_lex_pass_obsolete_entries (true);
482   catalog_reader_parse ((abstract_catalog_reader_ty *) pop, fp, real_filename,
483                         logical_filename, input_syntax);
484   mdlp = pop->mdlp;
485   catalog_reader_free ((abstract_catalog_reader_ty *) pop);
486   return mdlp;
487 }
488 
489 
490 msgdomain_list_ty *
read_catalog_file(const char * filename,catalog_input_format_ty input_syntax)491 read_catalog_file (const char *filename, catalog_input_format_ty input_syntax)
492 {
493   char *real_filename;
494   FILE *fp = open_catalog_file (filename, &real_filename, true);
495   msgdomain_list_ty *result;
496 
497   result = read_catalog_stream (fp, real_filename, filename, input_syntax);
498 
499   if (fp != stdin)
500     fclose (fp);
501 
502   return result;
503 }
504