/* GNU gettext - internationalization aids Copyright (C) 1995-1998, 2000-2008, 2012, 2015-2016 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 . */ #ifdef HAVE_CONFIG_H # include #endif /* Specification. */ #include "write-catalog.h" #include #include #include #include #include #include #include #ifndef STDOUT_FILENO # define STDOUT_FILENO 1 #endif #include "ostream.h" #include "file-ostream.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 "styled-ostream.h" # include "term-styled-ostream.h" # include "html-styled-ostream.h" # include "fd-ostream.h" # include "color.h" # include "po-charset.h" # include "msgl-iconv.h" #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)))) { 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 (); stream = term_styled_ostream_create (fd, filename, style_file_name); if (stream == NULL) stream = fd_ostream_create (fd, filename, true); 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 (); html_stream = html_styled_ostream_create (stream, style_file_name); output_syntax->print (mdlp, html_stream, page_width, debug); ostream_free (html_stream); } else #endif { output_syntax->print (mdlp, stream, page_width, debug); } 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. */ if (a->filepos_count == 0) { if (b->filepos_count != 0) return -1; } if (b->filepos_count == 0) return 1; /* 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); } }