1/* GNU gettext - internationalization aids
2   Copyright (C) 1995-1998, 2000-2006 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.  */
71static size_t page_width = PAGE_WIDTH;
72
73void
74message_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
92void
93msgdomain_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, _("\
127Cannot 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, _("\
130Cannot 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, _("\
163message 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, _("\
198message 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, _("\
203message 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);
224	  if (fd < 0)
225	    {
226	      const char *errno_description = strerror (errno);
227	      po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
228			 xasprintf ("%s: %s",
229				    xasprintf (_("cannot create output file \"%s\""),
230					       filename),
231				    errno_description));
232	    }
233	}
234      else
235	{
236	  fd = STDOUT_FILENO;
237	  filename = _("standard output");
238	}
239
240      style_file_prepare ();
241      stream = term_styled_ostream_create (fd, filename, style_file_name);
242      if (stream == NULL)
243	stream = fd_ostream_create (fd, filename, true);
244      output_syntax->print (mdlp, stream, page_width, debug);
245      ostream_free (stream);
246
247      /* Make sure nothing went wrong.  */
248      if (close (fd) < 0)
249	{
250	  const char *errno_description = strerror (errno);
251	  po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
252		     xasprintf ("%s: %s",
253				xasprintf (_("error while writing \"%s\" file"),
254					   filename),
255				errno_description));
256	}
257    }
258  else
259#endif
260    {
261      FILE *fp;
262      file_ostream_t stream;
263
264      /* Open the output file.  */
265      if (!to_stdout)
266	{
267	  fp = fopen (filename, "w");
268	  if (fp == NULL)
269	    {
270	      const char *errno_description = strerror (errno);
271	      po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
272			 xasprintf ("%s: %s",
273				    xasprintf (_("cannot create output file \"%s\""),
274					       filename),
275				    errno_description));
276	    }
277	}
278      else
279	{
280	  fp = stdout;
281	  filename = _("standard output");
282	}
283
284      stream = file_ostream_create (fp);
285
286#if ENABLE_COLOR
287      if (output_syntax->supports_color && color_mode == color_html)
288	{
289	  html_styled_ostream_t html_stream;
290
291	  /* Convert mdlp to UTF-8 encoding.  */
292	  if (mdlp->encoding != po_charset_utf8)
293	    {
294	      mdlp = msgdomain_list_copy (mdlp, 0);
295	      mdlp = iconv_msgdomain_list (mdlp, po_charset_utf8, false, NULL);
296	    }
297
298	  style_file_prepare ();
299	  html_stream = html_styled_ostream_create (stream, style_file_name);
300	  output_syntax->print (mdlp, html_stream, page_width, debug);
301	  ostream_free (html_stream);
302	}
303      else
304#endif
305	{
306	  output_syntax->print (mdlp, stream, page_width, debug);
307	}
308
309      ostream_free (stream);
310
311      /* Make sure nothing went wrong.  */
312      if (fwriteerror (fp))
313	{
314	  const char *errno_description = strerror (errno);
315	  po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
316		     xasprintf ("%s: %s",
317				xasprintf (_("error while writing \"%s\" file"),
318					   filename),
319				errno_description));
320	}
321    }
322}
323
324
325/* =============================== Sorting. ================================ */
326
327
328static int
329cmp_by_msgid (const void *va, const void *vb)
330{
331  const message_ty *a = *(const message_ty **) va;
332  const message_ty *b = *(const message_ty **) vb;
333  /* Because msgids normally contain only ASCII characters, it is OK to
334     sort them as if we were in the C locale. And strcoll() in the C locale
335     is the same as strcmp().  */
336  return strcmp (a->msgid, b->msgid);
337}
338
339
340void
341msgdomain_list_sort_by_msgid (msgdomain_list_ty *mdlp)
342{
343  size_t k;
344
345  for (k = 0; k < mdlp->nitems; k++)
346    {
347      message_list_ty *mlp = mdlp->item[k]->messages;
348
349      if (mlp->nitems > 0)
350	qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_msgid);
351    }
352}
353
354
355/* Sort the file positions of every message.  */
356
357static int
358cmp_filepos (const void *va, const void *vb)
359{
360  const lex_pos_ty *a = (const lex_pos_ty *) va;
361  const lex_pos_ty *b = (const lex_pos_ty *) vb;
362  int cmp;
363
364  cmp = strcmp (a->file_name, b->file_name);
365  if (cmp == 0)
366    cmp = (int) a->line_number - (int) b->line_number;
367
368  return cmp;
369}
370
371static void
372msgdomain_list_sort_filepos (msgdomain_list_ty *mdlp)
373{
374  size_t j, k;
375
376  for (k = 0; k < mdlp->nitems; k++)
377    {
378      message_list_ty *mlp = mdlp->item[k]->messages;
379
380      for (j = 0; j < mlp->nitems; j++)
381	{
382	  message_ty *mp = mlp->item[j];
383
384	  if (mp->filepos_count > 0)
385	    qsort (mp->filepos, mp->filepos_count, sizeof (mp->filepos[0]),
386		   cmp_filepos);
387	}
388    }
389}
390
391
392/* Sort the messages according to the file position.  */
393
394static int
395cmp_by_filepos (const void *va, const void *vb)
396{
397  const message_ty *a = *(const message_ty **) va;
398  const message_ty *b = *(const message_ty **) vb;
399  int cmp;
400
401  /* No filepos is smaller than any other filepos.  */
402  if (a->filepos_count == 0)
403    {
404      if (b->filepos_count != 0)
405	return -1;
406    }
407  if (b->filepos_count == 0)
408    return 1;
409
410  /* Compare on the file names...  */
411  cmp = strcmp (a->filepos[0].file_name, b->filepos[0].file_name);
412  if (cmp != 0)
413    return cmp;
414
415  /* If they are equal, compare on the line numbers...  */
416  cmp = a->filepos[0].line_number - b->filepos[0].line_number;
417  if (cmp != 0)
418    return cmp;
419
420  /* If they are equal, compare on the msgid strings.  */
421  /* Because msgids normally contain only ASCII characters, it is OK to
422     sort them as if we were in the C locale. And strcoll() in the C locale
423     is the same as strcmp().  */
424  return strcmp (a->msgid, b->msgid);
425}
426
427
428void
429msgdomain_list_sort_by_filepos (msgdomain_list_ty *mdlp)
430{
431  size_t k;
432
433  /* It makes sense to compare filepos[0] of different messages only after
434     the filepos[] array of each message has been sorted.  Sort it now.  */
435  msgdomain_list_sort_filepos (mdlp);
436
437  for (k = 0; k < mdlp->nitems; k++)
438    {
439      message_list_ty *mlp = mdlp->item[k]->messages;
440
441      if (mlp->nitems > 0)
442	qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_filepos);
443    }
444}
445