1 /* GNU gettext - internationalization aids
2 Copyright (C) 1995-1998, 2000-2008 Free Software Foundation, Inc.
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>. */
22 #include "write-catalog.h"
33 # define STDOUT_FILENO 1
37 #include "file-ostream.h"
38 #include "fwriteerror.h"
39 #include "error-progname.h"
40 #include "xvasprintf.h"
41 #include "po-xerror.h"
44 /* Our regular abbreviation. */
45 #define _(str) gettext (str)
47 /* When compiled in src, enable color support.
48 When compiled in libgettextpo, don't enable color support. */
51 # define ENABLE_COLOR 1
53 # include "styled-ostream.h"
54 # include "term-styled-ostream.h"
55 # include "html-styled-ostream.h"
56 # include "fd-ostream.h"
59 # include "po-charset.h"
60 # include "msgl-iconv.h"
65 /* =========== Some parameters for use by 'msgdomain_list_print'. ========== */
68 /* This variable controls the page width when printing messages.
69 Defaults to PAGE_WIDTH if not set. Zero (0) given to message_page_-
70 width_set will result in no wrapping being performed. */
71 static size_t page_width = PAGE_WIDTH;
74 message_page_width_set (size_t n)
89 /* ======================== msgdomain_list_print() ======================== */
93 msgdomain_list_print (msgdomain_list_ty *mdlp, const char *filename,
94 catalog_output_format_ty output_syntax,
95 bool force, bool debug)
99 /* We will not write anything if, for every domain, we have no message
100 or only the header entry. */
103 bool found_nonempty = false;
106 for (k = 0; k < mdlp->nitems; k++)
108 message_list_ty *mlp = mdlp->item[k]->messages;
110 if (!(mlp->nitems == 0
111 || (mlp->nitems == 1 && is_header (mlp->item[0]))))
113 found_nonempty = true;
122 /* Check whether the output format can accomodate all messages. */
123 if (!output_syntax->supports_multiple_domains && mdlp->nitems > 1)
125 if (output_syntax->alternative_is_po)
126 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, _("\
127 Cannot output multiple translation domains into a single file with the specified output format. Try using PO file syntax instead."));
129 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, _("\
130 Cannot output multiple translation domains into a single file with the specified output format."));
134 if (!output_syntax->supports_contexts)
136 const lex_pos_ty *has_context;
140 for (k = 0; k < mdlp->nitems; k++)
142 message_list_ty *mlp = mdlp->item[k]->messages;
145 for (j = 0; j < mlp->nitems; j++)
147 message_ty *mp = mlp->item[j];
149 if (mp->msgctxt != NULL)
151 has_context = &mp->pos;
157 if (has_context != NULL)
159 error_with_progname = false;
160 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
161 has_context->file_name, has_context->line_number,
162 (size_t)(-1), false, _("\
163 message catalog has context dependent translations, but the output format does not support them."));
164 error_with_progname = true;
168 if (!output_syntax->supports_plurals)
170 const lex_pos_ty *has_plural;
174 for (k = 0; k < mdlp->nitems; k++)
176 message_list_ty *mlp = mdlp->item[k]->messages;
179 for (j = 0; j < mlp->nitems; j++)
181 message_ty *mp = mlp->item[j];
183 if (mp->msgid_plural != NULL)
185 has_plural = &mp->pos;
191 if (has_plural != NULL)
193 error_with_progname = false;
194 if (output_syntax->alternative_is_java_class)
195 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
196 has_plural->file_name, has_plural->line_number,
197 (size_t)(-1), false, _("\
198 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."));
200 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
201 has_plural->file_name, has_plural->line_number,
202 (size_t)(-1), false, _("\
203 message catalog has plural form translations, but the output format does not support them."));
204 error_with_progname = true;
209 to_stdout = (filename == NULL || strcmp (filename, "-") == 0
210 || strcmp (filename, "/dev/stdout") == 0);
213 if (output_syntax->supports_color
214 && (color_mode == color_yes
215 || (color_mode == color_tty && to_stdout && isatty (STDOUT_FILENO))))
220 /* Open the output file. */
223 fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC,
224 /* 0666 in portable POSIX notation: */
225 S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
228 const char *errno_description = strerror (errno);
229 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
231 xasprintf (_("cannot create output file \"%s\""),
239 filename = _("standard output");
242 style_file_prepare ();
243 stream = term_styled_ostream_create (fd, filename, style_file_name);
245 stream = fd_ostream_create (fd, filename, true);
246 output_syntax->print (mdlp, stream, page_width, debug);
247 ostream_free (stream);
249 /* Make sure nothing went wrong. */
252 const char *errno_description = strerror (errno);
253 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
255 xasprintf (_("error while writing \"%s\" file"),
264 file_ostream_t stream;
266 /* Open the output file. */
269 fp = fopen (filename, "wb");
272 const char *errno_description = strerror (errno);
273 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
275 xasprintf (_("cannot create output file \"%s\""),
283 filename = _("standard output");
286 stream = file_ostream_create (fp);
289 if (output_syntax->supports_color && color_mode == color_html)
291 html_styled_ostream_t html_stream;
293 /* Convert mdlp to UTF-8 encoding. */
294 if (mdlp->encoding != po_charset_utf8)
296 mdlp = msgdomain_list_copy (mdlp, 0);
297 mdlp = iconv_msgdomain_list (mdlp, po_charset_utf8, false, NULL);
300 style_file_prepare ();
301 html_stream = html_styled_ostream_create (stream, style_file_name);
302 output_syntax->print (mdlp, html_stream, page_width, debug);
303 ostream_free (html_stream);
308 output_syntax->print (mdlp, stream, page_width, debug);
311 ostream_free (stream);
313 /* Make sure nothing went wrong. */
314 if (fwriteerror (fp))
316 const char *errno_description = strerror (errno);
317 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
319 xasprintf (_("error while writing \"%s\" file"),
327 /* =============================== Sorting. ================================ */
331 cmp_by_msgid (const void *va, const void *vb)
333 const message_ty *a = *(const message_ty **) va;
334 const message_ty *b = *(const message_ty **) vb;
336 /* Because msgids normally contain only ASCII characters or are UTF-8
337 encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And
338 strcoll() in a C.UTF-8 locale is the same as strcmp(). */
339 int cmp = strcmp (a->msgid, b->msgid);
343 /* If the msgids are equal, disambiguate by comparing the contexts. */
344 if (a->msgctxt == b->msgctxt)
346 if (a->msgctxt == NULL)
348 if (b->msgctxt == NULL)
350 return strcmp (a->msgctxt, b->msgctxt);
355 msgdomain_list_sort_by_msgid (msgdomain_list_ty *mdlp)
359 for (k = 0; k < mdlp->nitems; k++)
361 message_list_ty *mlp = mdlp->item[k]->messages;
364 qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_msgid);
369 /* Sort the file positions of every message. */
372 cmp_filepos (const void *va, const void *vb)
374 const lex_pos_ty *a = (const lex_pos_ty *) va;
375 const lex_pos_ty *b = (const lex_pos_ty *) vb;
378 cmp = strcmp (a->file_name, b->file_name);
380 cmp = (int) a->line_number - (int) b->line_number;
386 msgdomain_list_sort_filepos (msgdomain_list_ty *mdlp)
390 for (k = 0; k < mdlp->nitems; k++)
392 message_list_ty *mlp = mdlp->item[k]->messages;
394 for (j = 0; j < mlp->nitems; j++)
396 message_ty *mp = mlp->item[j];
398 if (mp->filepos_count > 0)
399 qsort (mp->filepos, mp->filepos_count, sizeof (mp->filepos[0]),
406 /* Sort the messages according to the file position. */
409 cmp_by_filepos (const void *va, const void *vb)
411 const message_ty *a = *(const message_ty **) va;
412 const message_ty *b = *(const message_ty **) vb;
415 /* No filepos is smaller than any other filepos. */
416 if (a->filepos_count == 0)
418 if (b->filepos_count != 0)
421 if (b->filepos_count == 0)
424 /* Compare on the file names... */
425 cmp = strcmp (a->filepos[0].file_name, b->filepos[0].file_name);
429 /* If they are equal, compare on the line numbers... */
430 cmp = a->filepos[0].line_number - b->filepos[0].line_number;
434 /* If they are equal, compare on the msgid strings. */
435 /* Because msgids normally contain only ASCII characters or are UTF-8
436 encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And
437 strcoll() in a C.UTF-8 locale is the same as strcmp(). */
438 cmp = strcmp (a->msgid, b->msgid);
442 /* If the msgids are equal, disambiguate by comparing the contexts. */
443 if (a->msgctxt == b->msgctxt)
445 if (a->msgctxt == NULL)
447 if (b->msgctxt == NULL)
449 return strcmp (a->msgctxt, b->msgctxt);
454 msgdomain_list_sort_by_filepos (msgdomain_list_ty *mdlp)
458 /* It makes sense to compare filepos[0] of different messages only after
459 the filepos[] array of each message has been sorted. Sort it now. */
460 msgdomain_list_sort_filepos (mdlp);
462 for (k = 0; k < mdlp->nitems; k++)
464 message_list_ty *mlp = mdlp->item[k]->messages;
467 qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_filepos);