1 /* GNU gettext - internationalization aids
2 Copyright (C) 1995-1998, 2000-2008, 2012, 2019-2020 Free Software
3 Foundation, Inc.
4
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.
9
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.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
17
18 #ifdef HAVE_CONFIG_H
19 # include <config.h>
20 #endif
21
22 /* Specification. */
23 #include "write-catalog.h"
24
25 #include <errno.h>
26 #include <fcntl.h>
27 #include <limits.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include <unistd.h>
33 #ifndef STDOUT_FILENO
34 # define STDOUT_FILENO 1
35 #endif
36
37 #include <textstyle.h>
38
39 #include "fwriteerror.h"
40 #include "error-progname.h"
41 #include "xvasprintf.h"
42 #include "po-xerror.h"
43 #include "gettext.h"
44
45 /* Our regular abbreviation. */
46 #define _(str) gettext (str)
47
48 /* When compiled in src, enable color support.
49 When compiled in libgettextpo, don't enable color support. */
50 #ifdef GETTEXTDATADIR
51
52 # define ENABLE_COLOR 1
53
54 # include "relocatable.h"
55 # include "po-charset.h"
56 # include "msgl-iconv.h"
57
58 # define GETTEXTSTYLESDIR GETTEXTDATADIR "/styles"
59
60 #endif
61
62
63 /* =========== Some parameters for use by 'msgdomain_list_print'. ========== */
64
65
66 /* This variable controls the page width when printing messages.
67 Defaults to PAGE_WIDTH if not set. Zero (0) given to message_page_-
68 width_set will result in no wrapping being performed. */
69 static size_t page_width = PAGE_WIDTH;
70
71 void
message_page_width_set(size_t n)72 message_page_width_set (size_t n)
73 {
74 if (n == 0)
75 {
76 page_width = INT_MAX;
77 return;
78 }
79
80 if (n < 20)
81 n = 20;
82
83 page_width = n;
84 }
85
86
87 /* ======================== msgdomain_list_print() ======================== */
88
89
90 void
msgdomain_list_print(msgdomain_list_ty * mdlp,const char * filename,catalog_output_format_ty output_syntax,bool force,bool debug)91 msgdomain_list_print (msgdomain_list_ty *mdlp, const char *filename,
92 catalog_output_format_ty output_syntax,
93 bool force, bool debug)
94 {
95 bool to_stdout;
96
97 /* We will not write anything if, for every domain, we have no message
98 or only the header entry. */
99 if (!force)
100 {
101 bool found_nonempty = false;
102 size_t k;
103
104 for (k = 0; k < mdlp->nitems; k++)
105 {
106 message_list_ty *mlp = mdlp->item[k]->messages;
107
108 if (!(mlp->nitems == 0
109 || (mlp->nitems == 1 && is_header (mlp->item[0]))))
110 {
111 found_nonempty = true;
112 break;
113 }
114 }
115
116 if (!found_nonempty)
117 return;
118 }
119
120 /* Check whether the output format can accommodate all messages. */
121 if (!output_syntax->supports_multiple_domains && mdlp->nitems > 1)
122 {
123 if (output_syntax->alternative_is_po)
124 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
125 _("Cannot output multiple translation domains into a single file with the specified output format. Try using PO file syntax instead."));
126 else
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."));
129 }
130 else
131 {
132 if (!output_syntax->supports_contexts)
133 {
134 const lex_pos_ty *has_context;
135 size_t k;
136
137 has_context = NULL;
138 for (k = 0; k < mdlp->nitems; k++)
139 {
140 message_list_ty *mlp = mdlp->item[k]->messages;
141 size_t j;
142
143 for (j = 0; j < mlp->nitems; j++)
144 {
145 message_ty *mp = mlp->item[j];
146
147 if (mp->msgctxt != NULL)
148 {
149 has_context = &mp->pos;
150 break;
151 }
152 }
153 }
154
155 if (has_context != NULL)
156 {
157 error_with_progname = false;
158 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
159 has_context->file_name, has_context->line_number,
160 (size_t)(-1), false,
161 _("message catalog has context dependent translations, but the output format does not support them."));
162 error_with_progname = true;
163 }
164 }
165
166 if (!output_syntax->supports_plurals)
167 {
168 const lex_pos_ty *has_plural;
169 size_t k;
170
171 has_plural = NULL;
172 for (k = 0; k < mdlp->nitems; k++)
173 {
174 message_list_ty *mlp = mdlp->item[k]->messages;
175 size_t j;
176
177 for (j = 0; j < mlp->nitems; j++)
178 {
179 message_ty *mp = mlp->item[j];
180
181 if (mp->msgid_plural != NULL)
182 {
183 has_plural = &mp->pos;
184 break;
185 }
186 }
187 }
188
189 if (has_plural != NULL)
190 {
191 error_with_progname = false;
192 if (output_syntax->alternative_is_java_class)
193 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
194 has_plural->file_name, has_plural->line_number,
195 (size_t)(-1), false,
196 _("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."));
197 else
198 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
199 has_plural->file_name, has_plural->line_number,
200 (size_t)(-1), false,
201 _("message catalog has plural form translations, but the output format does not support them."));
202 error_with_progname = true;
203 }
204 }
205 }
206
207 to_stdout = (filename == NULL || strcmp (filename, "-") == 0
208 || strcmp (filename, "/dev/stdout") == 0);
209
210 #if ENABLE_COLOR
211 if (output_syntax->supports_color
212 && (color_mode == color_yes
213 || (color_mode == color_tty && to_stdout
214 && isatty (STDOUT_FILENO)
215 && getenv ("NO_COLOR") == NULL)))
216 {
217 int fd;
218 ostream_t stream;
219
220 /* Open the output file. */
221 if (!to_stdout)
222 {
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);
226 if (fd < 0)
227 {
228 const char *errno_description = strerror (errno);
229 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
230 xasprintf ("%s: %s",
231 xasprintf (_("cannot create output file \"%s\""),
232 filename),
233 errno_description));
234 }
235 }
236 else
237 {
238 fd = STDOUT_FILENO;
239 filename = _("standard output");
240 }
241
242 style_file_prepare ("PO_STYLE",
243 "GETTEXTSTYLESDIR", relocate (GETTEXTSTYLESDIR),
244 "po-default.css");
245 stream =
246 styled_ostream_create (fd, filename, TTYCTL_AUTO, style_file_name);
247 output_syntax->print (mdlp, stream, page_width, debug);
248 ostream_free (stream);
249
250 /* Make sure nothing went wrong. */
251 if (close (fd) < 0)
252 {
253 const char *errno_description = strerror (errno);
254 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
255 xasprintf ("%s: %s",
256 xasprintf (_("error while writing \"%s\" file"),
257 filename),
258 errno_description));
259 }
260 }
261 else
262 #endif
263 {
264 FILE *fp;
265 file_ostream_t stream;
266
267 /* Open the output file. */
268 if (!to_stdout)
269 {
270 fp = fopen (filename, "wb");
271 if (fp == NULL)
272 {
273 const char *errno_description = strerror (errno);
274 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
275 xasprintf ("%s: %s",
276 xasprintf (_("cannot create output file \"%s\""),
277 filename),
278 errno_description));
279 }
280 }
281 else
282 {
283 fp = stdout;
284 filename = _("standard output");
285 }
286
287 stream = file_ostream_create (fp);
288
289 #if ENABLE_COLOR
290 if (output_syntax->supports_color && color_mode == color_html)
291 {
292 html_styled_ostream_t html_stream;
293
294 /* Convert mdlp to UTF-8 encoding. */
295 if (mdlp->encoding != po_charset_utf8)
296 {
297 mdlp = msgdomain_list_copy (mdlp, 0);
298 mdlp = iconv_msgdomain_list (mdlp, po_charset_utf8, false, NULL);
299 }
300
301 style_file_prepare ("PO_STYLE",
302 "GETTEXTSTYLESDIR", relocate (GETTEXTSTYLESDIR),
303 "po-default.css");
304 html_stream = html_styled_ostream_create (stream, style_file_name);
305 output_syntax->print (mdlp, html_stream, page_width, debug);
306 ostream_free (html_stream);
307 }
308 else
309 {
310 noop_styled_ostream_t styled_stream;
311
312 styled_stream = noop_styled_ostream_create (stream, false);
313 output_syntax->print (mdlp, styled_stream, page_width, debug);
314 ostream_free (styled_stream);
315 }
316 #else
317 output_syntax->print (mdlp, stream, page_width, debug);
318 /* Don't call ostream_free if file_ostream_create is a dummy. */
319 if (stream != fp)
320 #endif
321 ostream_free (stream);
322
323 /* Make sure nothing went wrong. */
324 if (fwriteerror (fp))
325 {
326 const char *errno_description = strerror (errno);
327 po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
328 xasprintf ("%s: %s",
329 xasprintf (_("error while writing \"%s\" file"),
330 filename),
331 errno_description));
332 }
333 }
334 }
335
336
337 /* =============================== Sorting. ================================ */
338
339
340 static int
cmp_by_msgid(const void * va,const void * vb)341 cmp_by_msgid (const void *va, const void *vb)
342 {
343 const message_ty *a = *(const message_ty **) va;
344 const message_ty *b = *(const message_ty **) vb;
345
346 /* Because msgids normally contain only ASCII characters or are UTF-8
347 encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And
348 strcoll() in a C.UTF-8 locale is the same as strcmp(). */
349 int cmp = strcmp (a->msgid, b->msgid);
350 if (cmp != 0)
351 return cmp;
352
353 /* If the msgids are equal, disambiguate by comparing the contexts. */
354 if (a->msgctxt == b->msgctxt)
355 return 0;
356 if (a->msgctxt == NULL)
357 return -1;
358 if (b->msgctxt == NULL)
359 return 1;
360 return strcmp (a->msgctxt, b->msgctxt);
361 }
362
363
364 void
msgdomain_list_sort_by_msgid(msgdomain_list_ty * mdlp)365 msgdomain_list_sort_by_msgid (msgdomain_list_ty *mdlp)
366 {
367 size_t k;
368
369 for (k = 0; k < mdlp->nitems; k++)
370 {
371 message_list_ty *mlp = mdlp->item[k]->messages;
372
373 if (mlp->nitems > 0)
374 qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_msgid);
375 }
376 }
377
378
379 /* Sort the file positions of every message. */
380
381 static int
cmp_filepos(const void * va,const void * vb)382 cmp_filepos (const void *va, const void *vb)
383 {
384 const lex_pos_ty *a = (const lex_pos_ty *) va;
385 const lex_pos_ty *b = (const lex_pos_ty *) vb;
386 int cmp;
387
388 cmp = strcmp (a->file_name, b->file_name);
389 if (cmp == 0)
390 cmp = (int) a->line_number - (int) b->line_number;
391
392 return cmp;
393 }
394
395 static void
msgdomain_list_sort_filepos(msgdomain_list_ty * mdlp)396 msgdomain_list_sort_filepos (msgdomain_list_ty *mdlp)
397 {
398 size_t j, k;
399
400 for (k = 0; k < mdlp->nitems; k++)
401 {
402 message_list_ty *mlp = mdlp->item[k]->messages;
403
404 for (j = 0; j < mlp->nitems; j++)
405 {
406 message_ty *mp = mlp->item[j];
407
408 if (mp->filepos_count > 0)
409 qsort (mp->filepos, mp->filepos_count, sizeof (mp->filepos[0]),
410 cmp_filepos);
411 }
412 }
413 }
414
415
416 /* Sort the messages according to the file position. */
417
418 static int
cmp_by_filepos(const void * va,const void * vb)419 cmp_by_filepos (const void *va, const void *vb)
420 {
421 const message_ty *a = *(const message_ty **) va;
422 const message_ty *b = *(const message_ty **) vb;
423 int cmp;
424
425 /* No filepos is smaller than any other filepos. */
426 cmp = (a->filepos_count != 0) - (b->filepos_count != 0);
427 if (cmp != 0)
428 return cmp;
429
430 if (a->filepos_count != 0)
431 {
432 /* Compare on the file names... */
433 cmp = strcmp (a->filepos[0].file_name, b->filepos[0].file_name);
434 if (cmp != 0)
435 return cmp;
436
437 /* If they are equal, compare on the line numbers... */
438 cmp = a->filepos[0].line_number - b->filepos[0].line_number;
439 if (cmp != 0)
440 return cmp;
441 }
442
443 /* If they are equal, compare on the msgid strings. */
444 /* Because msgids normally contain only ASCII characters or are UTF-8
445 encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And
446 strcoll() in a C.UTF-8 locale is the same as strcmp(). */
447 cmp = strcmp (a->msgid, b->msgid);
448 if (cmp != 0)
449 return cmp;
450
451 /* If the msgids are equal, disambiguate by comparing the contexts. */
452 if (a->msgctxt == b->msgctxt)
453 return 0;
454 if (a->msgctxt == NULL)
455 return -1;
456 if (b->msgctxt == NULL)
457 return 1;
458 return strcmp (a->msgctxt, b->msgctxt);
459 }
460
461
462 void
msgdomain_list_sort_by_filepos(msgdomain_list_ty * mdlp)463 msgdomain_list_sort_by_filepos (msgdomain_list_ty *mdlp)
464 {
465 size_t k;
466
467 /* It makes sense to compare filepos[0] of different messages only after
468 the filepos[] array of each message has been sorted. Sort it now. */
469 msgdomain_list_sort_filepos (mdlp);
470
471 for (k = 0; k < mdlp->nitems; k++)
472 {
473 message_list_ty *mlp = mdlp->item[k]->messages;
474
475 if (mlp->nitems > 0)
476 qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_filepos);
477 }
478 }
479