xref: /netbsd-src/external/gpl2/gettext/dist/gettext-tools/src/read-catalog.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
1 /* Reading PO files.
2    Copyright (C) 1995-1998, 2000-2003, 2005-2006 Free Software Foundation, Inc.
3    This file was written by Peter Miller <millerp@canb.auug.org.au>
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
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->do_wrap = undecided;
107 }
108 
109 
110 void
default_destructor(abstract_catalog_reader_ty * that)111 default_destructor (abstract_catalog_reader_ty *that)
112 {
113   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
114 
115   /* Do not free this->mdlp and this->mlp.  */
116   if (this->handle_comments)
117     {
118       if (this->comment != NULL)
119 	string_list_free (this->comment);
120       if (this->comment_dot != NULL)
121 	string_list_free (this->comment_dot);
122     }
123   if (this->handle_filepos_comments)
124     {
125       size_t j;
126 
127       for (j = 0; j < this->filepos_count; ++j)
128 	free (this->filepos[j].file_name);
129       if (this->filepos != NULL)
130 	free (this->filepos);
131     }
132 }
133 
134 
135 void
default_parse_brief(abstract_catalog_reader_ty * that)136 default_parse_brief (abstract_catalog_reader_ty *that)
137 {
138   /* We need to parse comments, because even if this->handle_comments and
139      this->handle_filepos_comments are false, we need to know which messages
140      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   if (this->handle_filepos_comments)
167     {
168       for (j = 0; j < this->filepos_count; ++j)
169 	{
170 	  lex_pos_ty *pp;
171 
172 	  pp = &this->filepos[j];
173 	  message_comment_filepos (mp, pp->file_name, pp->line_number);
174 	}
175     }
176   mp->is_fuzzy = this->is_fuzzy;
177   for (i = 0; i < NFORMATS; i++)
178     mp->is_format[i] = this->is_format[i];
179   mp->do_wrap = this->do_wrap;
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   if (this->handle_filepos_comments)
202     {
203       for (j = 0; j < this->filepos_count; ++j)
204 	free (this->filepos[j].file_name);
205       if (this->filepos != NULL)
206 	free (this->filepos);
207       this->filepos_count = 0;
208       this->filepos = NULL;
209     }
210   this->is_fuzzy = false;
211   for (i = 0; i < NFORMATS; i++)
212     this->is_format[i] = undecided;
213   this->do_wrap = undecided;
214 }
215 
216 
217 /* Process 'domain' directive from .po file.  */
218 void
default_directive_domain(abstract_catalog_reader_ty * that,char * name)219 default_directive_domain (abstract_catalog_reader_ty *that, char *name)
220 {
221   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
222 
223   call_set_domain (this, name);
224 
225   /* If there are accumulated comments, throw them away, they are
226      probably part of the file header, or about the domain directive,
227      and will be unrelated to the next message.  */
228   default_reset_comment_state (this);
229 }
230 
231 
232 /* Process ['msgctxt'/]'msgid'/'msgstr' pair from .po file.  */
233 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)234 default_directive_message (abstract_catalog_reader_ty *that,
235 			   char *msgctxt,
236 			   char *msgid,
237 			   lex_pos_ty *msgid_pos,
238 			   char *msgid_plural,
239 			   char *msgstr, size_t msgstr_len,
240 			   lex_pos_ty *msgstr_pos,
241 			   char *prev_msgctxt,
242 			   char *prev_msgid, char *prev_msgid_plural,
243 			   bool force_fuzzy, bool obsolete)
244 {
245   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
246 
247   call_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural,
248 		    msgstr, msgstr_len, msgstr_pos,
249 		    prev_msgctxt, prev_msgid, prev_msgid_plural,
250 		    force_fuzzy, obsolete);
251 
252   /* Prepare for next message.  */
253   default_reset_comment_state (this);
254 }
255 
256 
257 void
default_comment(abstract_catalog_reader_ty * that,const char * s)258 default_comment (abstract_catalog_reader_ty *that, const char *s)
259 {
260   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
261 
262   if (this->handle_comments)
263     {
264       if (this->comment == NULL)
265 	this->comment = string_list_alloc ();
266       string_list_append (this->comment, s);
267     }
268 }
269 
270 
271 void
default_comment_dot(abstract_catalog_reader_ty * that,const char * s)272 default_comment_dot (abstract_catalog_reader_ty *that, const char *s)
273 {
274   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
275 
276   if (this->handle_comments)
277     {
278       if (this->comment_dot == NULL)
279 	this->comment_dot = string_list_alloc ();
280       string_list_append (this->comment_dot, s);
281     }
282 }
283 
284 
285 void
default_comment_filepos(abstract_catalog_reader_ty * that,const char * name,size_t line)286 default_comment_filepos (abstract_catalog_reader_ty *that,
287 			 const char *name, size_t line)
288 {
289   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
290 
291   if (this->handle_filepos_comments)
292     {
293       size_t nbytes;
294       lex_pos_ty *pp;
295 
296       nbytes = (this->filepos_count + 1) * sizeof (this->filepos[0]);
297       this->filepos = xrealloc (this->filepos, nbytes);
298       pp = &this->filepos[this->filepos_count++];
299       pp->file_name = xstrdup (name);
300       pp->line_number = line;
301     }
302 }
303 
304 
305 /* Test for '#, fuzzy' comments and warn.  */
306 void
default_comment_special(abstract_catalog_reader_ty * that,const char * s)307 default_comment_special (abstract_catalog_reader_ty *that, const char *s)
308 {
309   default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
310 
311   po_parse_comment_special (s, &this->is_fuzzy, this->is_format,
312 			    &this->do_wrap);
313 }
314 
315 
316 /* Default implementation of methods not inherited from the superclass.  */
317 
318 
319 void
default_set_domain(default_catalog_reader_ty * this,char * name)320 default_set_domain (default_catalog_reader_ty *this, char *name)
321 {
322   if (this->allow_domain_directives)
323     /* Override current domain name.  Don't free memory.  */
324     this->domain = name;
325   else
326     {
327       po_gram_error_at_line (&gram_pos,
328 			     _("this file may not contain domain directives"));
329 
330       /* NAME was allocated in po-gram-gen.y but is not used anywhere.  */
331       free (name);
332     }
333 }
334 
335 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)336 default_add_message (default_catalog_reader_ty *this,
337 		     char *msgctxt,
338 		     char *msgid,
339 		     lex_pos_ty *msgid_pos,
340 		     char *msgid_plural,
341 		     char *msgstr, size_t msgstr_len,
342 		     lex_pos_ty *msgstr_pos,
343 		     char *prev_msgctxt,
344 		     char *prev_msgid,
345 		     char *prev_msgid_plural,
346 		     bool force_fuzzy, bool obsolete)
347 {
348   message_ty *mp;
349 
350   if (this->mdlp != NULL)
351     /* Select the appropriate sublist of this->mdlp.  */
352     this->mlp = msgdomain_list_sublist (this->mdlp, this->domain, true);
353 
354   if (this->allow_duplicates && msgid[0] != '\0')
355     /* Doesn't matter if this message ID has been seen before.  */
356     mp = NULL;
357   else
358     /* See if this message ID has been seen before.  */
359     mp = message_list_search (this->mlp, msgctxt, msgid);
360 
361   if (mp)
362     {
363       if (!(this->allow_duplicates_if_same_msgstr
364 	    && msgstr_len == mp->msgstr_len
365 	    && memcmp (msgstr, mp->msgstr, msgstr_len) == 0))
366 	{
367 	  /* We give a fatal error about this, regardless whether the
368 	     translations are equal or different.  This is for consistency
369 	     with msgmerge, msgcat and others.  The user can use the
370 	     msguniq program to get rid of duplicates.  */
371 	  po_xerror2 (PO_SEVERITY_ERROR,
372 		      NULL, msgid_pos->file_name, msgid_pos->line_number,
373 		      (size_t)(-1), false, _("duplicate message definition"),
374 		      mp, NULL, 0, 0, false,
375 		      _("this is the location of the first definition"));
376 	}
377       /* We don't need the just constructed entries' parameter string
378 	 (allocated in po-gram-gen.y).  */
379       free (msgid);
380       if (msgid_plural != NULL)
381 	free (msgid_plural);
382       free (msgstr);
383       if (msgctxt != NULL)
384 	free (msgctxt);
385       if (prev_msgctxt != NULL)
386 	free (prev_msgctxt);
387       if (prev_msgid != NULL)
388 	free (prev_msgid);
389       if (prev_msgid_plural != NULL)
390 	free (prev_msgid_plural);
391 
392       /* Add the accumulated comments to the message.  */
393       default_copy_comment_state (this, mp);
394     }
395   else
396     {
397       /* Construct message to add to the list.
398 	 Obsolete message go into the list at least for duplicate checking.
399 	 It's the caller's responsibility to ignore obsolete messages when
400 	 appropriate.  */
401       mp = message_alloc (msgctxt, msgid, msgid_plural, msgstr, msgstr_len,
402 			  msgstr_pos);
403       mp->prev_msgctxt = prev_msgctxt;
404       mp->prev_msgid = prev_msgid;
405       mp->prev_msgid_plural = prev_msgid_plural;
406       mp->obsolete = obsolete;
407       default_copy_comment_state (this, mp);
408       if (force_fuzzy)
409 	mp->is_fuzzy = true;
410 
411       call_frob_new_message (this, mp, msgid_pos, msgstr_pos);
412 
413       message_list_append (this->mlp, mp);
414     }
415 }
416 
417 
418 /* So that the one parser can be used for multiple programs, and also
419    use good data hiding and encapsulation practices, an object
420    oriented approach has been taken.  An object instance is allocated,
421    and all actions resulting from the parse will be through
422    invocations of method functions of that object.  */
423 
424 static default_catalog_reader_class_ty default_methods =
425 {
426   {
427     sizeof (default_catalog_reader_ty),
428     default_constructor,
429     default_destructor,
430     default_parse_brief,
431     default_parse_debrief,
432     default_directive_domain,
433     default_directive_message,
434     default_comment,
435     default_comment_dot,
436     default_comment_filepos,
437     default_comment_special
438   },
439   default_set_domain, /* set_domain */
440   default_add_message, /* add_message */
441   NULL /* frob_new_message */
442 };
443 
444 
445 default_catalog_reader_ty *
default_catalog_reader_alloc(default_catalog_reader_class_ty * method_table)446 default_catalog_reader_alloc (default_catalog_reader_class_ty *method_table)
447 {
448   return
449     (default_catalog_reader_ty *) catalog_reader_alloc (&method_table->super);
450 }
451 
452 
453 /* ========================================================================= */
454 /* Exported functions.  */
455 
456 
457 /* If nonzero, remember comments for file name and line number for each
458    msgid, if present in the reference input.  Defaults to true.  */
459 int line_comment = 1;
460 
461 /* If false, duplicate msgids in the same domain and file generate an error.
462    If true, such msgids are allowed; the caller should treat them
463    appropriately.  Defaults to false.  */
464 bool allow_duplicates = false;
465 
466 
467 msgdomain_list_ty *
read_catalog_stream(FILE * fp,const char * real_filename,const char * logical_filename,catalog_input_format_ty input_syntax)468 read_catalog_stream (FILE *fp, const char *real_filename,
469 		     const char *logical_filename,
470 		     catalog_input_format_ty input_syntax)
471 {
472   default_catalog_reader_ty *pop;
473   msgdomain_list_ty *mdlp;
474 
475   pop = default_catalog_reader_alloc (&default_methods);
476   pop->handle_comments = true;
477   pop->handle_filepos_comments = (line_comment != 0);
478   pop->allow_domain_directives = true;
479   pop->allow_duplicates = allow_duplicates;
480   pop->allow_duplicates_if_same_msgstr = false;
481   pop->mdlp = msgdomain_list_alloc (!pop->allow_duplicates);
482   pop->mlp = msgdomain_list_sublist (pop->mdlp, pop->domain, true);
483   if (input_syntax->produces_utf8)
484     /* We know a priori that input_syntax->parse convert strings to UTF-8.  */
485     pop->mdlp->encoding = po_charset_utf8;
486   po_lex_pass_obsolete_entries (true);
487   catalog_reader_parse ((abstract_catalog_reader_ty *) pop, fp, real_filename,
488 			logical_filename, input_syntax);
489   mdlp = pop->mdlp;
490   catalog_reader_free ((abstract_catalog_reader_ty *) pop);
491   return mdlp;
492 }
493 
494 
495 msgdomain_list_ty *
read_catalog_file(const char * filename,catalog_input_format_ty input_syntax)496 read_catalog_file (const char *filename, catalog_input_format_ty input_syntax)
497 {
498   char *real_filename;
499   FILE *fp = open_catalog_file (filename, &real_filename, true);
500   msgdomain_list_ty *result;
501 
502   result = read_catalog_stream (fp, real_filename, filename, input_syntax);
503 
504   if (fp != stdin)
505     fclose (fp);
506 
507   return result;
508 }
509