Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / write-catalog.c
1 /* GNU gettext - internationalization aids
2    Copyright (C) 1995-1998, 2000-2008, 2012, 2015 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 <http://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 "ostream.h"
38 #include "file-ostream.h"
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 "styled-ostream.h"
55 # include "term-styled-ostream.h"
56 # include "html-styled-ostream.h"
57 # include "fd-ostream.h"
58
59 # include "color.h"
60 # include "po-charset.h"
61 # include "msgl-iconv.h"
62
63 #endif
64
65
66 /* =========== Some parameters for use by 'msgdomain_list_print'. ========== */
67
68
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;
73
74 void
75 message_page_width_set (size_t n)
76 {
77   if (n == 0)
78     {
79       page_width = INT_MAX;
80       return;
81     }
82
83   if (n < 20)
84     n = 20;
85
86   page_width = n;
87 }
88
89
90 /* ======================== msgdomain_list_print() ======================== */
91
92
93 void
94 msgdomain_list_print (msgdomain_list_ty *mdlp, const char *filename,
95                       catalog_output_format_ty output_syntax,
96                       bool force, bool debug)
97 {
98   bool to_stdout;
99
100   /* We will not write anything if, for every domain, we have no message
101      or only the header entry.  */
102   if (!force)
103     {
104       bool found_nonempty = false;
105       size_t k;
106
107       for (k = 0; k < mdlp->nitems; k++)
108         {
109           message_list_ty *mlp = mdlp->item[k]->messages;
110
111           if (!(mlp->nitems == 0
112                 || (mlp->nitems == 1 && is_header (mlp->item[0]))))
113             {
114               found_nonempty = true;
115               break;
116             }
117         }
118
119       if (!found_nonempty)
120         return;
121     }
122
123   /* Check whether the output format can accommodate all messages.  */
124   if (!output_syntax->supports_multiple_domains && mdlp->nitems > 1)
125     {
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."));
129       else
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."));
132     }
133   else
134     {
135       if (!output_syntax->supports_contexts)
136         {
137           const lex_pos_ty *has_context;
138           size_t k;
139
140           has_context = NULL;
141           for (k = 0; k < mdlp->nitems; k++)
142             {
143               message_list_ty *mlp = mdlp->item[k]->messages;
144               size_t j;
145
146               for (j = 0; j < mlp->nitems; j++)
147                 {
148                   message_ty *mp = mlp->item[j];
149
150                   if (mp->msgctxt != NULL)
151                     {
152                       has_context = &mp->pos;
153                       break;
154                     }
155                 }
156             }
157
158           if (has_context != NULL)
159             {
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;
166             }
167         }
168
169       if (!output_syntax->supports_plurals)
170         {
171           const lex_pos_ty *has_plural;
172           size_t k;
173
174           has_plural = NULL;
175           for (k = 0; k < mdlp->nitems; k++)
176             {
177               message_list_ty *mlp = mdlp->item[k]->messages;
178               size_t j;
179
180               for (j = 0; j < mlp->nitems; j++)
181                 {
182                   message_ty *mp = mlp->item[j];
183
184                   if (mp->msgid_plural != NULL)
185                     {
186                       has_plural = &mp->pos;
187                       break;
188                     }
189                 }
190             }
191
192           if (has_plural != NULL)
193             {
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."));
200               else
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;
206             }
207         }
208     }
209
210   to_stdout = (filename == NULL || strcmp (filename, "-") == 0
211                || strcmp (filename, "/dev/stdout") == 0);
212
213 #if ENABLE_COLOR
214   if (output_syntax->supports_color
215       && (color_mode == color_yes
216           || (color_mode == color_tty && to_stdout && isatty (STDOUT_FILENO))))
217     {
218       int fd;
219       ostream_t stream;
220
221       /* Open the output file.  */
222       if (!to_stdout)
223         {
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);
227           if (fd < 0)
228             {
229               const char *errno_description = strerror (errno);
230               po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
231                          xasprintf ("%s: %s",
232                                     xasprintf (_("cannot create output file \"%s\""),
233                                                filename),
234                                     errno_description));
235             }
236         }
237       else
238         {
239           fd = STDOUT_FILENO;
240           filename = _("standard output");
241         }
242
243       style_file_prepare ();
244       stream = term_styled_ostream_create (fd, filename, style_file_name);
245       if (stream == NULL)
246         stream = fd_ostream_create (fd, filename, true);
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 ();
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);
305         }
306       else
307 #endif
308         {
309           output_syntax->print (mdlp, stream, page_width, debug);
310         }
311
312       ostream_free (stream);
313
314       /* Make sure nothing went wrong.  */
315       if (fwriteerror (fp))
316         {
317           const char *errno_description = strerror (errno);
318           po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
319                      xasprintf ("%s: %s",
320                                 xasprintf (_("error while writing \"%s\" file"),
321                                            filename),
322                                 errno_description));
323         }
324     }
325 }
326
327
328 /* =============================== Sorting. ================================ */
329
330
331 static int
332 cmp_by_msgid (const void *va, const void *vb)
333 {
334   const message_ty *a = *(const message_ty **) va;
335   const message_ty *b = *(const message_ty **) vb;
336
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);
341   if (cmp != 0)
342     return cmp;
343
344   /* If the msgids are equal, disambiguate by comparing the contexts.  */
345   if (a->msgctxt == b->msgctxt)
346     return 0;
347   if (a->msgctxt == NULL)
348     return -1;
349   if (b->msgctxt == NULL)
350     return 1;
351   return strcmp (a->msgctxt, b->msgctxt);
352 }
353
354
355 void
356 msgdomain_list_sort_by_msgid (msgdomain_list_ty *mdlp)
357 {
358   size_t k;
359
360   for (k = 0; k < mdlp->nitems; k++)
361     {
362       message_list_ty *mlp = mdlp->item[k]->messages;
363
364       if (mlp->nitems > 0)
365         qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_msgid);
366     }
367 }
368
369
370 /* Sort the file positions of every message.  */
371
372 static int
373 cmp_filepos (const void *va, const void *vb)
374 {
375   const lex_pos_ty *a = (const lex_pos_ty *) va;
376   const lex_pos_ty *b = (const lex_pos_ty *) vb;
377   int cmp;
378
379   cmp = strcmp (a->file_name, b->file_name);
380   if (cmp == 0)
381     cmp = (int) a->line_number - (int) b->line_number;
382
383   return cmp;
384 }
385
386 static void
387 msgdomain_list_sort_filepos (msgdomain_list_ty *mdlp)
388 {
389   size_t j, k;
390
391   for (k = 0; k < mdlp->nitems; k++)
392     {
393       message_list_ty *mlp = mdlp->item[k]->messages;
394
395       for (j = 0; j < mlp->nitems; j++)
396         {
397           message_ty *mp = mlp->item[j];
398
399           if (mp->filepos_count > 0)
400             qsort (mp->filepos, mp->filepos_count, sizeof (mp->filepos[0]),
401                    cmp_filepos);
402         }
403     }
404 }
405
406
407 /* Sort the messages according to the file position.  */
408
409 static int
410 cmp_by_filepos (const void *va, const void *vb)
411 {
412   const message_ty *a = *(const message_ty **) va;
413   const message_ty *b = *(const message_ty **) vb;
414   int cmp;
415
416   /* No filepos is smaller than any other filepos.  */
417   if (a->filepos_count == 0)
418     {
419       if (b->filepos_count != 0)
420         return -1;
421     }
422   if (b->filepos_count == 0)
423     return 1;
424
425   /* Compare on the file names...  */
426   cmp = strcmp (a->filepos[0].file_name, b->filepos[0].file_name);
427   if (cmp != 0)
428     return cmp;
429
430   /* If they are equal, compare on the line numbers...  */
431   cmp = a->filepos[0].line_number - b->filepos[0].line_number;
432   if (cmp != 0)
433     return cmp;
434
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);
440   if (cmp != 0)
441     return cmp;
442
443   /* If the msgids are equal, disambiguate by comparing the contexts.  */
444   if (a->msgctxt == b->msgctxt)
445     return 0;
446   if (a->msgctxt == NULL)
447     return -1;
448   if (b->msgctxt == NULL)
449     return 1;
450   return strcmp (a->msgctxt, b->msgctxt);
451 }
452
453
454 void
455 msgdomain_list_sort_by_filepos (msgdomain_list_ty *mdlp)
456 {
457   size_t k;
458
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);
462
463   for (k = 0; k < mdlp->nitems; k++)
464     {
465       message_list_ty *mlp = mdlp->item[k]->messages;
466
467       if (mlp->nitems > 0)
468         qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_filepos);
469     }
470 }