Imported Upstream version 0.18.1.1
[platform/upstream/gettext.git] / gettext-tools / src / write-catalog.c
1 /* GNU gettext - internationalization aids
2    Copyright (C) 1995-1998, 2000-2008 Free Software Foundation, Inc.
3
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.
8
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.
13
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/>.  */
16
17 #ifdef HAVE_CONFIG_H
18 # include <config.h>
19 #endif
20
21 /* Specification.  */
22 #include "write-catalog.h"
23
24 #include <errno.h>
25 #include <fcntl.h>
26 #include <limits.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30
31 #include <unistd.h>
32 #ifndef STDOUT_FILENO
33 # define STDOUT_FILENO 1
34 #endif
35
36 #include "ostream.h"
37 #include "file-ostream.h"
38 #include "fwriteerror.h"
39 #include "error-progname.h"
40 #include "xvasprintf.h"
41 #include "po-xerror.h"
42 #include "gettext.h"
43
44 /* Our regular abbreviation.  */
45 #define _(str) gettext (str)
46
47 /* When compiled in src, enable color support.
48    When compiled in libgettextpo, don't enable color support.  */
49 #ifdef GETTEXTDATADIR
50
51 # define ENABLE_COLOR 1
52
53 # include "styled-ostream.h"
54 # include "term-styled-ostream.h"
55 # include "html-styled-ostream.h"
56 # include "fd-ostream.h"
57
58 # include "color.h"
59 # include "po-charset.h"
60 # include "msgl-iconv.h"
61
62 #endif
63
64
65 /* =========== Some parameters for use by 'msgdomain_list_print'. ========== */
66
67
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;
72
73 void
74 message_page_width_set (size_t n)
75 {
76   if (n == 0)
77     {
78       page_width = INT_MAX;
79       return;
80     }
81
82   if (n < 20)
83     n = 20;
84
85   page_width = n;
86 }
87
88
89 /* ======================== msgdomain_list_print() ======================== */
90
91
92 void
93 msgdomain_list_print (msgdomain_list_ty *mdlp, const char *filename,
94                       catalog_output_format_ty output_syntax,
95                       bool force, bool debug)
96 {
97   bool to_stdout;
98
99   /* We will not write anything if, for every domain, we have no message
100      or only the header entry.  */
101   if (!force)
102     {
103       bool found_nonempty = false;
104       size_t k;
105
106       for (k = 0; k < mdlp->nitems; k++)
107         {
108           message_list_ty *mlp = mdlp->item[k]->messages;
109
110           if (!(mlp->nitems == 0
111                 || (mlp->nitems == 1 && is_header (mlp->item[0]))))
112             {
113               found_nonempty = true;
114               break;
115             }
116         }
117
118       if (!found_nonempty)
119         return;
120     }
121
122   /* Check whether the output format can accomodate all messages.  */
123   if (!output_syntax->supports_multiple_domains && mdlp->nitems > 1)
124     {
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."));
128       else
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."));
131     }
132   else
133     {
134       if (!output_syntax->supports_contexts)
135         {
136           const lex_pos_ty *has_context;
137           size_t k;
138
139           has_context = NULL;
140           for (k = 0; k < mdlp->nitems; k++)
141             {
142               message_list_ty *mlp = mdlp->item[k]->messages;
143               size_t j;
144
145               for (j = 0; j < mlp->nitems; j++)
146                 {
147                   message_ty *mp = mlp->item[j];
148
149                   if (mp->msgctxt != NULL)
150                     {
151                       has_context = &mp->pos;
152                       break;
153                     }
154                 }
155             }
156
157           if (has_context != NULL)
158             {
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;
165             }
166         }
167
168       if (!output_syntax->supports_plurals)
169         {
170           const lex_pos_ty *has_plural;
171           size_t k;
172
173           has_plural = NULL;
174           for (k = 0; k < mdlp->nitems; k++)
175             {
176               message_list_ty *mlp = mdlp->item[k]->messages;
177               size_t j;
178
179               for (j = 0; j < mlp->nitems; j++)
180                 {
181                   message_ty *mp = mlp->item[j];
182
183                   if (mp->msgid_plural != NULL)
184                     {
185                       has_plural = &mp->pos;
186                       break;
187                     }
188                 }
189             }
190
191           if (has_plural != NULL)
192             {
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."));
199               else
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;
205             }
206         }
207     }
208
209   to_stdout = (filename == NULL || strcmp (filename, "-") == 0
210                || strcmp (filename, "/dev/stdout") == 0);
211
212 #if ENABLE_COLOR
213   if (output_syntax->supports_color
214       && (color_mode == color_yes
215           || (color_mode == color_tty && to_stdout && isatty (STDOUT_FILENO))))
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 ();
243       stream = term_styled_ostream_create (fd, filename, style_file_name);
244       if (stream == NULL)
245         stream = fd_ostream_create (fd, filename, true);
246       output_syntax->print (mdlp, stream, page_width, debug);
247       ostream_free (stream);
248
249       /* Make sure nothing went wrong.  */
250       if (close (fd) < 0)
251         {
252           const char *errno_description = strerror (errno);
253           po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
254                      xasprintf ("%s: %s",
255                                 xasprintf (_("error while writing \"%s\" file"),
256                                            filename),
257                                 errno_description));
258         }
259     }
260   else
261 #endif
262     {
263       FILE *fp;
264       file_ostream_t stream;
265
266       /* Open the output file.  */
267       if (!to_stdout)
268         {
269           fp = fopen (filename, "wb");
270           if (fp == NULL)
271             {
272               const char *errno_description = strerror (errno);
273               po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
274                          xasprintf ("%s: %s",
275                                     xasprintf (_("cannot create output file \"%s\""),
276                                                filename),
277                                     errno_description));
278             }
279         }
280       else
281         {
282           fp = stdout;
283           filename = _("standard output");
284         }
285
286       stream = file_ostream_create (fp);
287
288 #if ENABLE_COLOR
289       if (output_syntax->supports_color && color_mode == color_html)
290         {
291           html_styled_ostream_t html_stream;
292
293           /* Convert mdlp to UTF-8 encoding.  */
294           if (mdlp->encoding != po_charset_utf8)
295             {
296               mdlp = msgdomain_list_copy (mdlp, 0);
297               mdlp = iconv_msgdomain_list (mdlp, po_charset_utf8, false, NULL);
298             }
299
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);
304         }
305       else
306 #endif
307         {
308           output_syntax->print (mdlp, stream, page_width, debug);
309         }
310
311       ostream_free (stream);
312
313       /* Make sure nothing went wrong.  */
314       if (fwriteerror (fp))
315         {
316           const char *errno_description = strerror (errno);
317           po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
318                      xasprintf ("%s: %s",
319                                 xasprintf (_("error while writing \"%s\" file"),
320                                            filename),
321                                 errno_description));
322         }
323     }
324 }
325
326
327 /* =============================== Sorting. ================================ */
328
329
330 static int
331 cmp_by_msgid (const void *va, const void *vb)
332 {
333   const message_ty *a = *(const message_ty **) va;
334   const message_ty *b = *(const message_ty **) vb;
335
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);
340   if (cmp != 0)
341     return cmp;
342
343   /* If the msgids are equal, disambiguate by comparing the contexts.  */
344   if (a->msgctxt == b->msgctxt)
345     return 0;
346   if (a->msgctxt == NULL)
347     return -1;
348   if (b->msgctxt == NULL)
349     return 1;
350   return strcmp (a->msgctxt, b->msgctxt);
351 }
352
353
354 void
355 msgdomain_list_sort_by_msgid (msgdomain_list_ty *mdlp)
356 {
357   size_t k;
358
359   for (k = 0; k < mdlp->nitems; k++)
360     {
361       message_list_ty *mlp = mdlp->item[k]->messages;
362
363       if (mlp->nitems > 0)
364         qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_msgid);
365     }
366 }
367
368
369 /* Sort the file positions of every message.  */
370
371 static int
372 cmp_filepos (const void *va, const void *vb)
373 {
374   const lex_pos_ty *a = (const lex_pos_ty *) va;
375   const lex_pos_ty *b = (const lex_pos_ty *) vb;
376   int cmp;
377
378   cmp = strcmp (a->file_name, b->file_name);
379   if (cmp == 0)
380     cmp = (int) a->line_number - (int) b->line_number;
381
382   return cmp;
383 }
384
385 static void
386 msgdomain_list_sort_filepos (msgdomain_list_ty *mdlp)
387 {
388   size_t j, k;
389
390   for (k = 0; k < mdlp->nitems; k++)
391     {
392       message_list_ty *mlp = mdlp->item[k]->messages;
393
394       for (j = 0; j < mlp->nitems; j++)
395         {
396           message_ty *mp = mlp->item[j];
397
398           if (mp->filepos_count > 0)
399             qsort (mp->filepos, mp->filepos_count, sizeof (mp->filepos[0]),
400                    cmp_filepos);
401         }
402     }
403 }
404
405
406 /* Sort the messages according to the file position.  */
407
408 static int
409 cmp_by_filepos (const void *va, const void *vb)
410 {
411   const message_ty *a = *(const message_ty **) va;
412   const message_ty *b = *(const message_ty **) vb;
413   int cmp;
414
415   /* No filepos is smaller than any other filepos.  */
416   if (a->filepos_count == 0)
417     {
418       if (b->filepos_count != 0)
419         return -1;
420     }
421   if (b->filepos_count == 0)
422     return 1;
423
424   /* Compare on the file names...  */
425   cmp = strcmp (a->filepos[0].file_name, b->filepos[0].file_name);
426   if (cmp != 0)
427     return cmp;
428
429   /* If they are equal, compare on the line numbers...  */
430   cmp = a->filepos[0].line_number - b->filepos[0].line_number;
431   if (cmp != 0)
432     return cmp;
433
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);
439   if (cmp != 0)
440     return cmp;
441
442   /* If the msgids are equal, disambiguate by comparing the contexts.  */
443   if (a->msgctxt == b->msgctxt)
444     return 0;
445   if (a->msgctxt == NULL)
446     return -1;
447   if (b->msgctxt == NULL)
448     return 1;
449   return strcmp (a->msgctxt, b->msgctxt);
450 }
451
452
453 void
454 msgdomain_list_sort_by_filepos (msgdomain_list_ty *mdlp)
455 {
456   size_t k;
457
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);
461
462   for (k = 0; k < mdlp->nitems; k++)
463     {
464       message_list_ty *mlp = mdlp->item[k]->messages;
465
466       if (mlp->nitems > 0)
467         qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_filepos);
468     }
469 }