| /* Reading PO files. |
| Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014-2016 Free |
| Software Foundation, Inc. |
| This file was written by Peter Miller <millerp@canb.auug.org.au> |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <https://www.gnu.org/licenses/>. */ |
| |
| #ifdef HAVE_CONFIG_H |
| # include <config.h> |
| #endif |
| |
| /* Specification. */ |
| #include "read-catalog.h" |
| |
| #include <stdbool.h> |
| #include <stdlib.h> |
| #include <string.h> |
| |
| #include "open-catalog.h" |
| #include "po-charset.h" |
| #include "po-xerror.h" |
| #include "xalloc.h" |
| #include "gettext.h" |
| |
| #define _(str) gettext (str) |
| |
| |
| /* ========================================================================= */ |
| /* Inline functions to invoke the methods. */ |
| |
| static inline void |
| call_set_domain (struct default_catalog_reader_ty *this, char *name) |
| { |
| default_catalog_reader_class_ty *methods = |
| (default_catalog_reader_class_ty *) this->methods; |
| |
| if (methods->set_domain) |
| methods->set_domain (this, name); |
| } |
| |
| 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) |
| { |
| default_catalog_reader_class_ty *methods = |
| (default_catalog_reader_class_ty *) this->methods; |
| |
| if (methods->add_message) |
| methods->add_message (this, msgctxt, |
| msgid, msgid_pos, msgid_plural, |
| msgstr, msgstr_len, msgstr_pos, |
| prev_msgctxt, prev_msgid, prev_msgid_plural, |
| force_fuzzy, obsolete); |
| } |
| |
| 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) |
| { |
| default_catalog_reader_class_ty *methods = |
| (default_catalog_reader_class_ty *) this->methods; |
| |
| if (methods->frob_new_message) |
| methods->frob_new_message (this, mp, msgid_pos, msgstr_pos); |
| } |
| |
| |
| /* ========================================================================= */ |
| /* Implementation of default_catalog_reader_ty's methods. */ |
| |
| |
| /* Implementation of methods declared in the superclass. */ |
| |
| |
| /* Prepare for first message. */ |
| void |
| default_constructor (abstract_catalog_reader_ty *that) |
| { |
| default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; |
| size_t i; |
| |
| this->domain = MESSAGE_DOMAIN_DEFAULT; |
| this->comment = NULL; |
| this->comment_dot = NULL; |
| this->filepos_count = 0; |
| this->filepos = NULL; |
| this->is_fuzzy = false; |
| for (i = 0; i < NFORMATS; i++) |
| this->is_format[i] = undecided; |
| this->range.min = -1; |
| this->range.max = -1; |
| this->do_wrap = undecided; |
| for (i = 0; i < NSYNTAXCHECKS; i++) |
| this->do_syntax_check[i] = undecided; |
| } |
| |
| |
| void |
| default_destructor (abstract_catalog_reader_ty *that) |
| { |
| default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; |
| size_t j; |
| |
| /* Do not free this->mdlp and this->mlp. */ |
| if (this->handle_comments) |
| { |
| if (this->comment != NULL) |
| string_list_free (this->comment); |
| if (this->comment_dot != NULL) |
| string_list_free (this->comment_dot); |
| } |
| |
| for (j = 0; j < this->filepos_count; ++j) |
| free (this->filepos[j].file_name); |
| if (this->filepos != NULL) |
| free (this->filepos); |
| } |
| |
| |
| void |
| default_parse_brief (abstract_catalog_reader_ty *that) |
| { |
| /* We need to parse comments, because even if this->handle_comments |
| is false, we need to know which messages are fuzzy. */ |
| po_lex_pass_comments (true); |
| } |
| |
| |
| void |
| default_parse_debrief (abstract_catalog_reader_ty *that) |
| { |
| } |
| |
| |
| /* Add the accumulated comments to the message. */ |
| static void |
| default_copy_comment_state (default_catalog_reader_ty *this, message_ty *mp) |
| { |
| size_t j, i; |
| |
| if (this->handle_comments) |
| { |
| if (this->comment != NULL) |
| for (j = 0; j < this->comment->nitems; ++j) |
| message_comment_append (mp, this->comment->item[j]); |
| if (this->comment_dot != NULL) |
| for (j = 0; j < this->comment_dot->nitems; ++j) |
| message_comment_dot_append (mp, this->comment_dot->item[j]); |
| } |
| for (j = 0; j < this->filepos_count; ++j) |
| { |
| lex_pos_ty *pp; |
| |
| pp = &this->filepos[j]; |
| message_comment_filepos (mp, pp->file_name, pp->line_number); |
| } |
| mp->is_fuzzy = this->is_fuzzy; |
| for (i = 0; i < NFORMATS; i++) |
| mp->is_format[i] = this->is_format[i]; |
| mp->range = this->range; |
| mp->do_wrap = this->do_wrap; |
| for (i = 0; i < NSYNTAXCHECKS; i++) |
| mp->do_syntax_check[i] = this->do_syntax_check[i]; |
| } |
| |
| |
| static void |
| default_reset_comment_state (default_catalog_reader_ty *this) |
| { |
| size_t j, i; |
| |
| if (this->handle_comments) |
| { |
| if (this->comment != NULL) |
| { |
| string_list_free (this->comment); |
| this->comment = NULL; |
| } |
| if (this->comment_dot != NULL) |
| { |
| string_list_free (this->comment_dot); |
| this->comment_dot = NULL; |
| } |
| } |
| for (j = 0; j < this->filepos_count; ++j) |
| free (this->filepos[j].file_name); |
| if (this->filepos != NULL) |
| free (this->filepos); |
| this->filepos_count = 0; |
| this->filepos = NULL; |
| this->is_fuzzy = false; |
| for (i = 0; i < NFORMATS; i++) |
| this->is_format[i] = undecided; |
| this->range.min = -1; |
| this->range.max = -1; |
| this->do_wrap = undecided; |
| for (i = 0; i < NSYNTAXCHECKS; i++) |
| this->do_syntax_check[i] = undecided; |
| } |
| |
| |
| /* Process 'domain' directive from .po file. */ |
| void |
| default_directive_domain (abstract_catalog_reader_ty *that, char *name) |
| { |
| default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; |
| |
| call_set_domain (this, name); |
| |
| /* If there are accumulated comments, throw them away, they are |
| probably part of the file header, or about the domain directive, |
| and will be unrelated to the next message. */ |
| default_reset_comment_state (this); |
| } |
| |
| |
| /* Process ['msgctxt'/]'msgid'/'msgstr' pair from .po file. */ |
| 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) |
| { |
| default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; |
| |
| call_add_message (this, msgctxt, msgid, msgid_pos, msgid_plural, |
| msgstr, msgstr_len, msgstr_pos, |
| prev_msgctxt, prev_msgid, prev_msgid_plural, |
| force_fuzzy, obsolete); |
| |
| /* Prepare for next message. */ |
| default_reset_comment_state (this); |
| } |
| |
| |
| void |
| default_comment (abstract_catalog_reader_ty *that, const char *s) |
| { |
| default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; |
| |
| if (this->handle_comments) |
| { |
| if (this->comment == NULL) |
| this->comment = string_list_alloc (); |
| string_list_append (this->comment, s); |
| } |
| } |
| |
| |
| void |
| default_comment_dot (abstract_catalog_reader_ty *that, const char *s) |
| { |
| default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; |
| |
| if (this->handle_comments) |
| { |
| if (this->comment_dot == NULL) |
| this->comment_dot = string_list_alloc (); |
| string_list_append (this->comment_dot, s); |
| } |
| } |
| |
| |
| void |
| default_comment_filepos (abstract_catalog_reader_ty *that, |
| const char *name, size_t line) |
| { |
| default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; |
| size_t nbytes; |
| lex_pos_ty *pp; |
| |
| nbytes = (this->filepos_count + 1) * sizeof (this->filepos[0]); |
| this->filepos = xrealloc (this->filepos, nbytes); |
| pp = &this->filepos[this->filepos_count++]; |
| pp->file_name = xstrdup (name); |
| pp->line_number = line; |
| } |
| |
| |
| /* Test for '#, fuzzy' comments and warn. */ |
| void |
| default_comment_special (abstract_catalog_reader_ty *that, const char *s) |
| { |
| default_catalog_reader_ty *this = (default_catalog_reader_ty *) that; |
| |
| po_parse_comment_special (s, &this->is_fuzzy, this->is_format, &this->range, |
| &this->do_wrap, this->do_syntax_check); |
| } |
| |
| |
| /* Default implementation of methods not inherited from the superclass. */ |
| |
| |
| void |
| default_set_domain (default_catalog_reader_ty *this, char *name) |
| { |
| if (this->allow_domain_directives) |
| /* Override current domain name. Don't free memory. */ |
| this->domain = name; |
| else |
| { |
| po_gram_error_at_line (&gram_pos, |
| _("this file may not contain domain directives")); |
| |
| /* NAME was allocated in po-gram-gen.y but is not used anywhere. */ |
| free (name); |
| } |
| } |
| |
| 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) |
| { |
| message_ty *mp; |
| |
| if (this->mdlp != NULL) |
| /* Select the appropriate sublist of this->mdlp. */ |
| this->mlp = msgdomain_list_sublist (this->mdlp, this->domain, true); |
| |
| if (this->allow_duplicates && msgid[0] != '\0') |
| /* Doesn't matter if this message ID has been seen before. */ |
| mp = NULL; |
| else |
| /* See if this message ID has been seen before. */ |
| mp = message_list_search (this->mlp, msgctxt, msgid); |
| |
| if (mp) |
| { |
| if (!(this->allow_duplicates_if_same_msgstr |
| && msgstr_len == mp->msgstr_len |
| && memcmp (msgstr, mp->msgstr, msgstr_len) == 0)) |
| { |
| /* We give a fatal error about this, regardless whether the |
| translations are equal or different. This is for consistency |
| with msgmerge, msgcat and others. The user can use the |
| msguniq program to get rid of duplicates. */ |
| po_xerror2 (PO_SEVERITY_ERROR, |
| NULL, msgid_pos->file_name, msgid_pos->line_number, |
| (size_t)(-1), false, _("duplicate message definition"), |
| mp, NULL, 0, 0, false, |
| _("this is the location of the first definition")); |
| } |
| /* We don't need the just constructed entries' parameter string |
| (allocated in po-gram-gen.y). */ |
| free (msgid); |
| if (msgid_plural != NULL) |
| free (msgid_plural); |
| free (msgstr); |
| if (msgctxt != NULL) |
| free (msgctxt); |
| if (prev_msgctxt != NULL) |
| free (prev_msgctxt); |
| if (prev_msgid != NULL) |
| free (prev_msgid); |
| if (prev_msgid_plural != NULL) |
| free (prev_msgid_plural); |
| |
| /* Add the accumulated comments to the message. */ |
| default_copy_comment_state (this, mp); |
| } |
| else |
| { |
| /* Construct message to add to the list. |
| Obsolete message go into the list at least for duplicate checking. |
| It's the caller's responsibility to ignore obsolete messages when |
| appropriate. */ |
| mp = message_alloc (msgctxt, msgid, msgid_plural, msgstr, msgstr_len, |
| msgstr_pos); |
| if (msgid_plural != NULL) |
| free (msgid_plural); |
| mp->prev_msgctxt = prev_msgctxt; |
| mp->prev_msgid = prev_msgid; |
| mp->prev_msgid_plural = prev_msgid_plural; |
| mp->obsolete = obsolete; |
| default_copy_comment_state (this, mp); |
| if (force_fuzzy) |
| mp->is_fuzzy = true; |
| |
| call_frob_new_message (this, mp, msgid_pos, msgstr_pos); |
| |
| message_list_append (this->mlp, mp); |
| } |
| } |
| |
| |
| /* So that the one parser can be used for multiple programs, and also |
| use good data hiding and encapsulation practices, an object |
| oriented approach has been taken. An object instance is allocated, |
| and all actions resulting from the parse will be through |
| invocations of method functions of that object. */ |
| |
| static default_catalog_reader_class_ty default_methods = |
| { |
| { |
| sizeof (default_catalog_reader_ty), |
| default_constructor, |
| default_destructor, |
| default_parse_brief, |
| default_parse_debrief, |
| default_directive_domain, |
| default_directive_message, |
| default_comment, |
| default_comment_dot, |
| default_comment_filepos, |
| default_comment_special |
| }, |
| default_set_domain, /* set_domain */ |
| default_add_message, /* add_message */ |
| NULL /* frob_new_message */ |
| }; |
| |
| |
| default_catalog_reader_ty * |
| default_catalog_reader_alloc (default_catalog_reader_class_ty *method_table) |
| { |
| return |
| (default_catalog_reader_ty *) catalog_reader_alloc (&method_table->super); |
| } |
| |
| |
| /* ========================================================================= */ |
| /* Exported functions. */ |
| |
| |
| /* If false, duplicate msgids in the same domain and file generate an error. |
| If true, such msgids are allowed; the caller should treat them |
| appropriately. Defaults to false. */ |
| bool allow_duplicates = false; |
| |
| |
| msgdomain_list_ty * |
| read_catalog_stream (FILE *fp, const char *real_filename, |
| const char *logical_filename, |
| catalog_input_format_ty input_syntax) |
| { |
| default_catalog_reader_ty *pop; |
| msgdomain_list_ty *mdlp; |
| |
| pop = default_catalog_reader_alloc (&default_methods); |
| pop->handle_comments = true; |
| pop->allow_domain_directives = true; |
| pop->allow_duplicates = allow_duplicates; |
| pop->allow_duplicates_if_same_msgstr = false; |
| pop->file_name = real_filename; |
| pop->mdlp = msgdomain_list_alloc (!pop->allow_duplicates); |
| pop->mlp = msgdomain_list_sublist (pop->mdlp, pop->domain, true); |
| if (input_syntax->produces_utf8) |
| /* We know a priori that input_syntax->parse convert strings to UTF-8. */ |
| pop->mdlp->encoding = po_charset_utf8; |
| po_lex_pass_obsolete_entries (true); |
| catalog_reader_parse ((abstract_catalog_reader_ty *) pop, fp, real_filename, |
| logical_filename, input_syntax); |
| mdlp = pop->mdlp; |
| catalog_reader_free ((abstract_catalog_reader_ty *) pop); |
| return mdlp; |
| } |
| |
| |
| msgdomain_list_ty * |
| read_catalog_file (const char *filename, catalog_input_format_ty input_syntax) |
| { |
| char *real_filename; |
| FILE *fp = open_catalog_file (filename, &real_filename, true); |
| msgdomain_list_ty *result; |
| |
| result = read_catalog_stream (fp, real_filename, filename, input_syntax); |
| |
| if (fp != stdin) |
| fclose (fp); |
| |
| return result; |
| } |