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>
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.
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.
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/>. */
24 #include "read-catalog.h"
30 #include "open-catalog.h"
31 #include "po-charset.h"
32 #include "po-xerror.h"
36 #define _(str) gettext (str)
39 /* ========================================================================= */
40 /* Inline functions to invoke the methods. */
43 call_set_domain (struct default_catalog_reader_ty *this, char *name)
45 default_catalog_reader_class_ty *methods =
46 (default_catalog_reader_class_ty *) this->methods;
48 if (methods->set_domain)
49 methods->set_domain (this, name);
53 call_add_message (struct default_catalog_reader_ty *this,
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)
60 default_catalog_reader_class_ty *methods =
61 (default_catalog_reader_class_ty *) this->methods;
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);
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)
76 default_catalog_reader_class_ty *methods =
77 (default_catalog_reader_class_ty *) this->methods;
79 if (methods->frob_new_message)
80 methods->frob_new_message (this, mp, msgid_pos, msgstr_pos);
84 /* ========================================================================= */
85 /* Implementation of default_catalog_reader_ty's methods. */
88 /* Implementation of methods declared in the superclass. */
91 /* Prepare for first message. */
93 default_constructor (abstract_catalog_reader_ty *that)
95 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
98 this->domain = MESSAGE_DOMAIN_DEFAULT;
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;
115 default_destructor (abstract_catalog_reader_ty *that)
117 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
120 /* Do not free this->mdlp and this->mlp. */
121 if (this->handle_comments)
123 if (this->comment != NULL)
124 string_list_free (this->comment);
125 if (this->comment_dot != NULL)
126 string_list_free (this->comment_dot);
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);
137 default_parse_brief (abstract_catalog_reader_ty *that)
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);
146 default_parse_debrief (abstract_catalog_reader_ty *that)
151 /* Add the accumulated comments to the message. */
153 default_copy_comment_state (default_catalog_reader_ty *this, message_ty *mp)
157 if (this->handle_comments)
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]);
166 for (j = 0; j < this->filepos_count; ++j)
170 pp = &this->filepos[j];
171 message_comment_filepos (mp, pp->file_name, pp->line_number);
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];
184 default_reset_comment_state (default_catalog_reader_ty *this)
188 if (this->handle_comments)
190 if (this->comment != NULL)
192 string_list_free (this->comment);
193 this->comment = NULL;
195 if (this->comment_dot != NULL)
197 string_list_free (this->comment_dot);
198 this->comment_dot = NULL;
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;
218 /* Process 'domain' directive from .po file. */
220 default_directive_domain (abstract_catalog_reader_ty *that, char *name)
222 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
224 call_set_domain (this, name);
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);
233 /* Process ['msgctxt'/]'msgid'/'msgstr' pair from .po file. */
235 default_directive_message (abstract_catalog_reader_ty *that,
238 lex_pos_ty *msgid_pos,
240 char *msgstr, size_t msgstr_len,
241 lex_pos_ty *msgstr_pos,
243 char *prev_msgid, char *prev_msgid_plural,
244 bool force_fuzzy, bool obsolete)
246 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
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);
253 /* Prepare for next message. */
254 default_reset_comment_state (this);
259 default_comment (abstract_catalog_reader_ty *that, const char *s)
261 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
263 if (this->handle_comments)
265 if (this->comment == NULL)
266 this->comment = string_list_alloc ();
267 string_list_append (this->comment, s);
273 default_comment_dot (abstract_catalog_reader_ty *that, const char *s)
275 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
277 if (this->handle_comments)
279 if (this->comment_dot == NULL)
280 this->comment_dot = string_list_alloc ();
281 string_list_append (this->comment_dot, s);
287 default_comment_filepos (abstract_catalog_reader_ty *that,
288 const char *name, size_t line)
290 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
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;
302 /* Test for '#, fuzzy' comments and warn. */
304 default_comment_special (abstract_catalog_reader_ty *that, const char *s)
306 default_catalog_reader_ty *this = (default_catalog_reader_ty *) that;
308 po_parse_comment_special (s, &this->is_fuzzy, this->is_format, &this->range,
309 &this->do_wrap, this->do_syntax_check);
313 /* Default implementation of methods not inherited from the superclass. */
317 default_set_domain (default_catalog_reader_ty *this, char *name)
319 if (this->allow_domain_directives)
320 /* Override current domain name. Don't free memory. */
324 po_gram_error_at_line (&gram_pos,
325 _("this file may not contain domain directives"));
327 /* NAME was allocated in po-gram-gen.y but is not used anywhere. */
333 default_add_message (default_catalog_reader_ty *this,
336 lex_pos_ty *msgid_pos,
338 char *msgstr, size_t msgstr_len,
339 lex_pos_ty *msgstr_pos,
342 char *prev_msgid_plural,
343 bool force_fuzzy, bool obsolete)
347 if (this->mdlp != NULL)
348 /* Select the appropriate sublist of this->mdlp. */
349 this->mlp = msgdomain_list_sublist (this->mdlp, this->domain, true);
351 if (this->allow_duplicates && msgid[0] != '\0')
352 /* Doesn't matter if this message ID has been seen before. */
355 /* See if this message ID has been seen before. */
356 mp = message_list_search (this->mlp, msgctxt, msgid);
360 if (!(this->allow_duplicates_if_same_msgstr
361 && msgstr_len == mp->msgstr_len
362 && memcmp (msgstr, mp->msgstr, msgstr_len) == 0))
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"));
374 /* We don't need the just constructed entries' parameter string
375 (allocated in po-gram-gen.y). */
377 if (msgid_plural != NULL)
382 if (prev_msgctxt != NULL)
384 if (prev_msgid != NULL)
386 if (prev_msgid_plural != NULL)
387 free (prev_msgid_plural);
389 /* Add the accumulated comments to the message. */
390 default_copy_comment_state (this, mp);
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
398 mp = message_alloc (msgctxt, msgid, msgid_plural, msgstr, msgstr_len,
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);
408 call_frob_new_message (this, mp, msgid_pos, msgstr_pos);
410 message_list_append (this->mlp, mp);
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. */
421 static default_catalog_reader_class_ty default_methods =
424 sizeof (default_catalog_reader_ty),
428 default_parse_debrief,
429 default_directive_domain,
430 default_directive_message,
433 default_comment_filepos,
434 default_comment_special
436 default_set_domain, /* set_domain */
437 default_add_message, /* add_message */
438 NULL /* frob_new_message */
442 default_catalog_reader_ty *
443 default_catalog_reader_alloc (default_catalog_reader_class_ty *method_table)
446 (default_catalog_reader_ty *) catalog_reader_alloc (&method_table->super);
450 /* ========================================================================= */
451 /* Exported functions. */
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;
461 read_catalog_stream (FILE *fp, const char *real_filename,
462 const char *logical_filename,
463 catalog_input_format_ty input_syntax)
465 default_catalog_reader_ty *pop;
466 msgdomain_list_ty *mdlp;
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);
483 catalog_reader_free ((abstract_catalog_reader_ty *) pop);
489 read_catalog_file (const char *filename, catalog_input_format_ty input_syntax)
492 FILE *fp = open_catalog_file (filename, &real_filename, true);
493 msgdomain_list_ty *result;
495 result = read_catalog_stream (fp, real_filename, filename, input_syntax);