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