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