facf0c92e0
Red Bear OS is a full fork. All sources must be available from git clone with zero network access. Removed gitignore rules that excluded fetched source trees under recipes/*/source/, local/recipes/kde/*/source/, local/recipes/qt/*/source/, and vendor source trees. Build artifacts (target/, build/, source.tar, *.o, *.so) remain excluded. 127291 files added — kernel, relibc, base, bootloader, pkgar, all KDE/Qt frameworks, mesa, wayland, DRM drivers, and every other recipe source.
503 lines
15 KiB
C
503 lines
15 KiB
C
/* Reading PO files.
|
|
Copyright (C) 1995-1998, 2000-2003, 2005-2006, 2008-2009, 2014-2016, 2023 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 ((char *) 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 ((char *) 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;
|
|
}
|