• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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