blob: c25916d1b403dc57257b85771c256bf833448f58 [file] [log] [blame]
/* GNU gettext - internationalization aids
Copyright (C) 1995-1998, 2000-2008, 2012, 2019-2020 Free Software
Foundation, Inc.
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 "write-catalog.h"
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifndef STDOUT_FILENO
# define STDOUT_FILENO 1
#endif
#include <textstyle.h>
#include "fwriteerror.h"
#include "error-progname.h"
#include "xvasprintf.h"
#include "po-xerror.h"
#include "gettext.h"
/* Our regular abbreviation. */
#define _(str) gettext (str)
/* When compiled in src, enable color support.
When compiled in libgettextpo, don't enable color support. */
#ifdef GETTEXTDATADIR
# define ENABLE_COLOR 1
# include "relocatable.h"
# include "po-charset.h"
# include "msgl-iconv.h"
# define GETTEXTSTYLESDIR GETTEXTDATADIR "/styles"
#endif
/* =========== Some parameters for use by 'msgdomain_list_print'. ========== */
/* This variable controls the page width when printing messages.
Defaults to PAGE_WIDTH if not set. Zero (0) given to message_page_-
width_set will result in no wrapping being performed. */
static size_t page_width = PAGE_WIDTH;
void
message_page_width_set (size_t n)
{
if (n == 0)
{
page_width = INT_MAX;
return;
}
if (n < 20)
n = 20;
page_width = n;
}
/* ======================== msgdomain_list_print() ======================== */
void
msgdomain_list_print (msgdomain_list_ty *mdlp, const char *filename,
catalog_output_format_ty output_syntax,
bool force, bool debug)
{
bool to_stdout;
/* We will not write anything if, for every domain, we have no message
or only the header entry. */
if (!force)
{
bool found_nonempty = false;
size_t k;
for (k = 0; k < mdlp->nitems; k++)
{
message_list_ty *mlp = mdlp->item[k]->messages;
if (!(mlp->nitems == 0
|| (mlp->nitems == 1 && is_header (mlp->item[0]))))
{
found_nonempty = true;
break;
}
}
if (!found_nonempty)
return;
}
/* Check whether the output format can accommodate all messages. */
if (!output_syntax->supports_multiple_domains && mdlp->nitems > 1)
{
if (output_syntax->alternative_is_po)
po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
_("Cannot output multiple translation domains into a single file with the specified output format. Try using PO file syntax instead."));
else
po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
_("Cannot output multiple translation domains into a single file with the specified output format."));
}
else
{
if (!output_syntax->supports_contexts)
{
const lex_pos_ty *has_context;
size_t k;
has_context = NULL;
for (k = 0; k < mdlp->nitems; k++)
{
message_list_ty *mlp = mdlp->item[k]->messages;
size_t j;
for (j = 0; j < mlp->nitems; j++)
{
message_ty *mp = mlp->item[j];
if (mp->msgctxt != NULL)
{
has_context = &mp->pos;
break;
}
}
}
if (has_context != NULL)
{
error_with_progname = false;
po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
has_context->file_name, has_context->line_number,
(size_t)(-1), false,
_("message catalog has context dependent translations, but the output format does not support them."));
error_with_progname = true;
}
}
if (!output_syntax->supports_plurals)
{
const lex_pos_ty *has_plural;
size_t k;
has_plural = NULL;
for (k = 0; k < mdlp->nitems; k++)
{
message_list_ty *mlp = mdlp->item[k]->messages;
size_t j;
for (j = 0; j < mlp->nitems; j++)
{
message_ty *mp = mlp->item[j];
if (mp->msgid_plural != NULL)
{
has_plural = &mp->pos;
break;
}
}
}
if (has_plural != NULL)
{
error_with_progname = false;
if (output_syntax->alternative_is_java_class)
po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
has_plural->file_name, has_plural->line_number,
(size_t)(-1), false,
_("message catalog has plural form translations, but the output format does not support them. Try generating a Java class using \"msgfmt --java\", instead of a properties file."));
else
po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
has_plural->file_name, has_plural->line_number,
(size_t)(-1), false,
_("message catalog has plural form translations, but the output format does not support them."));
error_with_progname = true;
}
}
}
to_stdout = (filename == NULL || strcmp (filename, "-") == 0
|| strcmp (filename, "/dev/stdout") == 0);
#if ENABLE_COLOR
if (output_syntax->supports_color
&& (color_mode == color_yes
|| (color_mode == color_tty && to_stdout
&& isatty (STDOUT_FILENO)
&& getenv ("NO_COLOR") == NULL)))
{
int fd;
ostream_t stream;
/* Open the output file. */
if (!to_stdout)
{
fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC,
/* 0666 in portable POSIX notation: */
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (fd < 0)
{
const char *errno_description = strerror (errno);
po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
xasprintf ("%s: %s",
xasprintf (_("cannot create output file \"%s\""),
filename),
errno_description));
}
}
else
{
fd = STDOUT_FILENO;
filename = _("standard output");
}
style_file_prepare ("PO_STYLE",
"GETTEXTSTYLESDIR", relocate (GETTEXTSTYLESDIR),
"po-default.css");
stream =
styled_ostream_create (fd, filename, TTYCTL_AUTO, style_file_name);
output_syntax->print (mdlp, stream, page_width, debug);
ostream_free (stream);
/* Make sure nothing went wrong. */
if (close (fd) < 0)
{
const char *errno_description = strerror (errno);
po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
xasprintf ("%s: %s",
xasprintf (_("error while writing \"%s\" file"),
filename),
errno_description));
}
}
else
#endif
{
FILE *fp;
file_ostream_t stream;
/* Open the output file. */
if (!to_stdout)
{
fp = fopen (filename, "wb");
if (fp == NULL)
{
const char *errno_description = strerror (errno);
po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
xasprintf ("%s: %s",
xasprintf (_("cannot create output file \"%s\""),
filename),
errno_description));
}
}
else
{
fp = stdout;
filename = _("standard output");
}
stream = file_ostream_create (fp);
#if ENABLE_COLOR
if (output_syntax->supports_color && color_mode == color_html)
{
html_styled_ostream_t html_stream;
/* Convert mdlp to UTF-8 encoding. */
if (mdlp->encoding != po_charset_utf8)
{
mdlp = msgdomain_list_copy (mdlp, 0);
mdlp = iconv_msgdomain_list (mdlp, po_charset_utf8, false, NULL);
}
style_file_prepare ("PO_STYLE",
"GETTEXTSTYLESDIR", relocate (GETTEXTSTYLESDIR),
"po-default.css");
html_stream = html_styled_ostream_create (stream, style_file_name);
output_syntax->print (mdlp, html_stream, page_width, debug);
ostream_free (html_stream);
}
else
{
noop_styled_ostream_t styled_stream;
styled_stream = noop_styled_ostream_create (stream, false);
output_syntax->print (mdlp, styled_stream, page_width, debug);
ostream_free (styled_stream);
}
#else
output_syntax->print (mdlp, stream, page_width, debug);
/* Don't call ostream_free if file_ostream_create is a dummy. */
if (stream != fp)
#endif
ostream_free (stream);
/* Make sure nothing went wrong. */
if (fwriteerror (fp))
{
const char *errno_description = strerror (errno);
po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
xasprintf ("%s: %s",
xasprintf (_("error while writing \"%s\" file"),
filename),
errno_description));
}
}
}
/* =============================== Sorting. ================================ */
static int
cmp_by_msgid (const void *va, const void *vb)
{
const message_ty *a = *(const message_ty **) va;
const message_ty *b = *(const message_ty **) vb;
/* Because msgids normally contain only ASCII characters or are UTF-8
encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And
strcoll() in a C.UTF-8 locale is the same as strcmp(). */
int cmp = strcmp (a->msgid, b->msgid);
if (cmp != 0)
return cmp;
/* If the msgids are equal, disambiguate by comparing the contexts. */
if (a->msgctxt == b->msgctxt)
return 0;
if (a->msgctxt == NULL)
return -1;
if (b->msgctxt == NULL)
return 1;
return strcmp (a->msgctxt, b->msgctxt);
}
void
msgdomain_list_sort_by_msgid (msgdomain_list_ty *mdlp)
{
size_t k;
for (k = 0; k < mdlp->nitems; k++)
{
message_list_ty *mlp = mdlp->item[k]->messages;
if (mlp->nitems > 0)
qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_msgid);
}
}
/* Sort the file positions of every message. */
static int
cmp_filepos (const void *va, const void *vb)
{
const lex_pos_ty *a = (const lex_pos_ty *) va;
const lex_pos_ty *b = (const lex_pos_ty *) vb;
int cmp;
cmp = strcmp (a->file_name, b->file_name);
if (cmp == 0)
cmp = (int) a->line_number - (int) b->line_number;
return cmp;
}
static void
msgdomain_list_sort_filepos (msgdomain_list_ty *mdlp)
{
size_t j, k;
for (k = 0; k < mdlp->nitems; k++)
{
message_list_ty *mlp = mdlp->item[k]->messages;
for (j = 0; j < mlp->nitems; j++)
{
message_ty *mp = mlp->item[j];
if (mp->filepos_count > 0)
qsort (mp->filepos, mp->filepos_count, sizeof (mp->filepos[0]),
cmp_filepos);
}
}
}
/* Sort the messages according to the file position. */
static int
cmp_by_filepos (const void *va, const void *vb)
{
const message_ty *a = *(const message_ty **) va;
const message_ty *b = *(const message_ty **) vb;
int cmp;
/* No filepos is smaller than any other filepos. */
cmp = (a->filepos_count != 0) - (b->filepos_count != 0);
if (cmp != 0)
return cmp;
if (a->filepos_count != 0)
{
/* Compare on the file names... */
cmp = strcmp (a->filepos[0].file_name, b->filepos[0].file_name);
if (cmp != 0)
return cmp;
/* If they are equal, compare on the line numbers... */
cmp = a->filepos[0].line_number - b->filepos[0].line_number;
if (cmp != 0)
return cmp;
}
/* If they are equal, compare on the msgid strings. */
/* Because msgids normally contain only ASCII characters or are UTF-8
encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And
strcoll() in a C.UTF-8 locale is the same as strcmp(). */
cmp = strcmp (a->msgid, b->msgid);
if (cmp != 0)
return cmp;
/* If the msgids are equal, disambiguate by comparing the contexts. */
if (a->msgctxt == b->msgctxt)
return 0;
if (a->msgctxt == NULL)
return -1;
if (b->msgctxt == NULL)
return 1;
return strcmp (a->msgctxt, b->msgctxt);
}
void
msgdomain_list_sort_by_filepos (msgdomain_list_ty *mdlp)
{
size_t k;
/* It makes sense to compare filepos[0] of different messages only after
the filepos[] array of each message has been sorted. Sort it now. */
msgdomain_list_sort_filepos (mdlp);
for (k = 0; k < mdlp->nitems; k++)
{
message_list_ty *mlp = mdlp->item[k]->messages;
if (mlp->nitems > 0)
qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_filepos);
}
}