1 /* GNU gettext - internationalization aids
2 Copyright (C) 1995-1998, 2000-2008, 2012, 2015 Free Software
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>. */
23 #include "write-catalog.h"
34 # define STDOUT_FILENO 1
38 #include "file-ostream.h"
39 #include "fwriteerror.h"
40 #include "error-progname.h"
41 #include "xvasprintf.h"
42 #include "po-xerror.h"
45 /* Our regular abbreviation. */
46 #define _(str) gettext (str)
48 /* When compiled in src, enable color support.
49 When compiled in libgettextpo, don't enable color support. */
52 # define ENABLE_COLOR 1
54 # include "styled-ostream.h"
55 # include "term-styled-ostream.h"
56 # include "html-styled-ostream.h"
57 # include "fd-ostream.h"
60 # include "po-charset.h"
61 # include "msgl-iconv.h"
66 /* =========== Some parameters for use by 'msgdomain_list_print'. ========== */
69 /* This variable controls the page width when printing messages.
70 Defaults to PAGE_WIDTH if not set. Zero (0) given to message_page_-
71 width_set will result in no wrapping being performed. */
72 static size_t page_width = PAGE_WIDTH;
75 message_page_width_set (size_t n)
90 /* ======================== msgdomain_list_print() ======================== */
94 msgdomain_list_print (msgdomain_list_ty *mdlp, const char *filename,
95 catalog_output_format_ty output_syntax,
96 bool force, bool debug)
100 /* We will not write anything if, for every domain, we have no message
101 or only the header entry. */
104 bool found_nonempty = false;
107 for (k = 0; k < mdlp->nitems; k++)
109 message_list_ty *mlp = mdlp->item[k]->messages;
111 if (!(mlp->nitems == 0
112 || (mlp->nitems == 1 && is_header (mlp->item[0]))))
114 found_nonempty = true;
123 /* Check whether the output format can accommodate all messages. */
124 if (!output_syntax->supports_multiple_domains && mdlp->nitems > 1)
126 if (output_syntax->alternative_is_po)
127 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, _("\
128 Cannot output multiple translation domains into a single file with the specified output format. Try using PO file syntax instead."));
130 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, _("\
131 Cannot output multiple translation domains into a single file with the specified output format."));
135 if (!output_syntax->supports_contexts)
137 const lex_pos_ty *has_context;
141 for (k = 0; k < mdlp->nitems; k++)
143 message_list_ty *mlp = mdlp->item[k]->messages;
146 for (j = 0; j < mlp->nitems; j++)
148 message_ty *mp = mlp->item[j];
150 if (mp->msgctxt != NULL)
152 has_context = &mp->pos;
158 if (has_context != NULL)
160 error_with_progname = false;
161 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
162 has_context->file_name, has_context->line_number,
163 (size_t)(-1), false, _("\
164 message catalog has context dependent translations, but the output format does not support them."));
165 error_with_progname = true;
169 if (!output_syntax->supports_plurals)
171 const lex_pos_ty *has_plural;
175 for (k = 0; k < mdlp->nitems; k++)
177 message_list_ty *mlp = mdlp->item[k]->messages;
180 for (j = 0; j < mlp->nitems; j++)
182 message_ty *mp = mlp->item[j];
184 if (mp->msgid_plural != NULL)
186 has_plural = &mp->pos;
192 if (has_plural != NULL)
194 error_with_progname = false;
195 if (output_syntax->alternative_is_java_class)
196 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
197 has_plural->file_name, has_plural->line_number,
198 (size_t)(-1), false, _("\
199 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."));
201 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
202 has_plural->file_name, has_plural->line_number,
203 (size_t)(-1), false, _("\
204 message catalog has plural form translations, but the output format does not support them."));
205 error_with_progname = true;
210 to_stdout = (filename == NULL || strcmp (filename, "-") == 0
211 || strcmp (filename, "/dev/stdout") == 0);
214 if (output_syntax->supports_color
215 && (color_mode == color_yes
216 || (color_mode == color_tty && to_stdout && isatty (STDOUT_FILENO))))
221 /* Open the output file. */
224 fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC,
225 /* 0666 in portable POSIX notation: */
226 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
229 const char *errno_description = strerror (errno);
230 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
232 xasprintf (_("cannot create output file \"%s\""),
240 filename = _("standard output");
243 style_file_prepare ();
244 stream = term_styled_ostream_create (fd, filename, style_file_name);
246 stream = fd_ostream_create (fd, filename, true);
247 output_syntax->print (mdlp, stream, page_width, debug);
248 ostream_free (stream);
250 /* Make sure nothing went wrong. */
253 const char *errno_description = strerror (errno);
254 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
256 xasprintf (_("error while writing \"%s\" file"),
265 file_ostream_t stream;
267 /* Open the output file. */
270 fp = fopen (filename, "wb");
273 const char *errno_description = strerror (errno);
274 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
276 xasprintf (_("cannot create output file \"%s\""),
284 filename = _("standard output");
287 stream = file_ostream_create (fp);
290 if (output_syntax->supports_color && color_mode == color_html)
292 html_styled_ostream_t html_stream;
294 /* Convert mdlp to UTF-8 encoding. */
295 if (mdlp->encoding != po_charset_utf8)
297 mdlp = msgdomain_list_copy (mdlp, 0);
298 mdlp = iconv_msgdomain_list (mdlp, po_charset_utf8, false, NULL);
301 style_file_prepare ();
302 html_stream = html_styled_ostream_create (stream, style_file_name);
303 output_syntax->print (mdlp, html_stream, page_width, debug);
304 ostream_free (html_stream);
309 output_syntax->print (mdlp, stream, page_width, debug);
312 ostream_free (stream);
314 /* Make sure nothing went wrong. */
315 if (fwriteerror (fp))
317 const char *errno_description = strerror (errno);
318 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
320 xasprintf (_("error while writing \"%s\" file"),
328 /* =============================== Sorting. ================================ */
332 cmp_by_msgid (const void *va, const void *vb)
334 const message_ty *a = *(const message_ty **) va;
335 const message_ty *b = *(const message_ty **) vb;
337 /* Because msgids normally contain only ASCII characters or are UTF-8
338 encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And
339 strcoll() in a C.UTF-8 locale is the same as strcmp(). */
340 int cmp = strcmp (a->msgid, b->msgid);
344 /* If the msgids are equal, disambiguate by comparing the contexts. */
345 if (a->msgctxt == b->msgctxt)
347 if (a->msgctxt == NULL)
349 if (b->msgctxt == NULL)
351 return strcmp (a->msgctxt, b->msgctxt);
356 msgdomain_list_sort_by_msgid (msgdomain_list_ty *mdlp)
360 for (k = 0; k < mdlp->nitems; k++)
362 message_list_ty *mlp = mdlp->item[k]->messages;
365 qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_msgid);
370 /* Sort the file positions of every message. */
373 cmp_filepos (const void *va, const void *vb)
375 const lex_pos_ty *a = (const lex_pos_ty *) va;
376 const lex_pos_ty *b = (const lex_pos_ty *) vb;
379 cmp = strcmp (a->file_name, b->file_name);
381 cmp = (int) a->line_number - (int) b->line_number;
387 msgdomain_list_sort_filepos (msgdomain_list_ty *mdlp)
391 for (k = 0; k < mdlp->nitems; k++)
393 message_list_ty *mlp = mdlp->item[k]->messages;
395 for (j = 0; j < mlp->nitems; j++)
397 message_ty *mp = mlp->item[j];
399 if (mp->filepos_count > 0)
400 qsort (mp->filepos, mp->filepos_count, sizeof (mp->filepos[0]),
407 /* Sort the messages according to the file position. */
410 cmp_by_filepos (const void *va, const void *vb)
412 const message_ty *a = *(const message_ty **) va;
413 const message_ty *b = *(const message_ty **) vb;
416 /* No filepos is smaller than any other filepos. */
417 if (a->filepos_count == 0)
419 if (b->filepos_count != 0)
422 if (b->filepos_count == 0)
425 /* Compare on the file names... */
426 cmp = strcmp (a->filepos[0].file_name, b->filepos[0].file_name);
430 /* If they are equal, compare on the line numbers... */
431 cmp = a->filepos[0].line_number - b->filepos[0].line_number;
435 /* If they are equal, compare on the msgid strings. */
436 /* Because msgids normally contain only ASCII characters or are UTF-8
437 encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And
438 strcoll() in a C.UTF-8 locale is the same as strcmp(). */
439 cmp = strcmp (a->msgid, b->msgid);
443 /* If the msgids are equal, disambiguate by comparing the contexts. */
444 if (a->msgctxt == b->msgctxt)
446 if (a->msgctxt == NULL)
448 if (b->msgctxt == NULL)
450 return strcmp (a->msgctxt, b->msgctxt);
455 msgdomain_list_sort_by_filepos (msgdomain_list_ty *mdlp)
459 /* It makes sense to compare filepos[0] of different messages only after
460 the filepos[] array of each message has been sorted. Sort it now. */
461 msgdomain_list_sort_filepos (mdlp);
463 for (k = 0; k < mdlp->nitems; k++)
465 message_list_ty *mlp = mdlp->item[k]->messages;
468 qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_filepos);