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 2, or (at your option)
7   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, write to the Free Software Foundation,
16   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
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 <limits.h>
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30
31#include "fwriteerror.h"
32#include "error-progname.h"
33#include "xvasprintf.h"
34#include "po-xerror.h"
35#include "gettext.h"
36
37/* Our regular abbreviation.  */
38#define _(str) gettext (str)
39
40
41/* =========== Some parameters for use by 'msgdomain_list_print'. ========== */
42
43
44/* This variable controls the page width when printing messages.
45   Defaults to PAGE_WIDTH if not set.  Zero (0) given to message_page_-
46   width_set will result in no wrapping being performed.  */
47static size_t page_width = PAGE_WIDTH;
48
49void
50message_page_width_set (size_t n)
51{
52  if (n == 0)
53    {
54      page_width = INT_MAX;
55      return;
56    }
57
58  if (n < 20)
59    n = 20;
60
61  page_width = n;
62}
63
64
65/* ======================== msgdomain_list_print() ======================== */
66
67
68void
69msgdomain_list_print (msgdomain_list_ty *mdlp, const char *filename,
70		      catalog_output_format_ty output_syntax,
71		      bool force, bool debug)
72{
73  FILE *fp;
74
75  /* We will not write anything if, for every domain, we have no message
76     or only the header entry.  */
77  if (!force)
78    {
79      bool found_nonempty = false;
80      size_t k;
81
82      for (k = 0; k < mdlp->nitems; k++)
83	{
84	  message_list_ty *mlp = mdlp->item[k]->messages;
85
86	  if (!(mlp->nitems == 0
87		|| (mlp->nitems == 1 && is_header (mlp->item[0]))))
88	    {
89	      found_nonempty = true;
90	      break;
91	    }
92	}
93
94      if (!found_nonempty)
95	return;
96    }
97
98  /* Check whether the output format can accomodate all messages.  */
99  if (!output_syntax->supports_multiple_domains && mdlp->nitems > 1)
100    {
101      if (output_syntax->alternative_is_po)
102	po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, _("\
103Cannot output multiple translation domains into a single file with the specified output format. Try using PO file syntax instead."));
104      else
105	po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false, _("\
106Cannot output multiple translation domains into a single file with the specified output format."));
107    }
108  else
109    {
110      if (!output_syntax->supports_contexts)
111	{
112	  const lex_pos_ty *has_context;
113	  size_t k;
114
115	  has_context = NULL;
116	  for (k = 0; k < mdlp->nitems; k++)
117	    {
118	      message_list_ty *mlp = mdlp->item[k]->messages;
119	      size_t j;
120
121	      for (j = 0; j < mlp->nitems; j++)
122		{
123		  message_ty *mp = mlp->item[j];
124
125		  if (mp->msgctxt != NULL)
126		    {
127		      has_context = &mp->pos;
128		      break;
129		    }
130		}
131	    }
132
133	  if (has_context != NULL)
134	    {
135	      error_with_progname = false;
136	      po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
137			 has_context->file_name, has_context->line_number,
138			 (size_t)(-1), false, _("\
139message catalog has context dependent translations, but the output format does not support them."));
140	      error_with_progname = true;
141	    }
142	}
143
144      if (!output_syntax->supports_plurals)
145	{
146	  const lex_pos_ty *has_plural;
147	  size_t k;
148
149	  has_plural = NULL;
150	  for (k = 0; k < mdlp->nitems; k++)
151	    {
152	      message_list_ty *mlp = mdlp->item[k]->messages;
153	      size_t j;
154
155	      for (j = 0; j < mlp->nitems; j++)
156		{
157		  message_ty *mp = mlp->item[j];
158
159		  if (mp->msgid_plural != NULL)
160		    {
161		      has_plural = &mp->pos;
162		      break;
163		    }
164		}
165	    }
166
167	  if (has_plural != NULL)
168	    {
169	      error_with_progname = false;
170	      if (output_syntax->alternative_is_java_class)
171		po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
172			   has_plural->file_name, has_plural->line_number,
173			   (size_t)(-1), false, _("\
174message 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."));
175	      else
176		po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
177			   has_plural->file_name, has_plural->line_number,
178			   (size_t)(-1), false, _("\
179message catalog has plural form translations, but the output format does not support them."));
180	      error_with_progname = true;
181	    }
182	}
183    }
184
185  /* Open the output file.  */
186  if (filename != NULL && strcmp (filename, "-") != 0
187      && strcmp (filename, "/dev/stdout") != 0)
188    {
189      fp = fopen (filename, "w");
190      if (fp == NULL)
191	{
192	  const char *errno_description = strerror (errno);
193	  po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
194		     xasprintf ("%s: %s",
195				xasprintf (_("cannot create output file \"%s\""),
196					   filename),
197				errno_description));
198	}
199    }
200  else
201    {
202      fp = stdout;
203      /* xgettext:no-c-format */
204      filename = _("standard output");
205    }
206
207  output_syntax->print (mdlp, fp, page_width, debug);
208
209  /* Make sure nothing went wrong.  */
210  if (fwriteerror (fp))
211    {
212      const char *errno_description = strerror (errno);
213      po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
214		 xasprintf ("%s: %s",
215			    xasprintf (_("error while writing \"%s\" file"),
216				       filename),
217			    errno_description));
218    }
219}
220
221
222/* =============================== Sorting. ================================ */
223
224
225static int
226cmp_by_msgid (const void *va, const void *vb)
227{
228  const message_ty *a = *(const message_ty **) va;
229  const message_ty *b = *(const message_ty **) vb;
230  /* Because msgids normally contain only ASCII characters, it is OK to
231     sort them as if we were in the C locale. And strcoll() in the C locale
232     is the same as strcmp().  */
233  return strcmp (a->msgid, b->msgid);
234}
235
236
237void
238msgdomain_list_sort_by_msgid (msgdomain_list_ty *mdlp)
239{
240  size_t k;
241
242  for (k = 0; k < mdlp->nitems; k++)
243    {
244      message_list_ty *mlp = mdlp->item[k]->messages;
245
246      if (mlp->nitems > 0)
247	qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_msgid);
248    }
249}
250
251
252/* Sort the file positions of every message.  */
253
254static int
255cmp_filepos (const void *va, const void *vb)
256{
257  const lex_pos_ty *a = (const lex_pos_ty *) va;
258  const lex_pos_ty *b = (const lex_pos_ty *) vb;
259  int cmp;
260
261  cmp = strcmp (a->file_name, b->file_name);
262  if (cmp == 0)
263    cmp = (int) a->line_number - (int) b->line_number;
264
265  return cmp;
266}
267
268static void
269msgdomain_list_sort_filepos (msgdomain_list_ty *mdlp)
270{
271  size_t j, k;
272
273  for (k = 0; k < mdlp->nitems; k++)
274    {
275      message_list_ty *mlp = mdlp->item[k]->messages;
276
277      for (j = 0; j < mlp->nitems; j++)
278	{
279	  message_ty *mp = mlp->item[j];
280
281	  if (mp->filepos_count > 0)
282	    qsort (mp->filepos, mp->filepos_count, sizeof (mp->filepos[0]),
283		   cmp_filepos);
284	}
285    }
286}
287
288
289/* Sort the messages according to the file position.  */
290
291static int
292cmp_by_filepos (const void *va, const void *vb)
293{
294  const message_ty *a = *(const message_ty **) va;
295  const message_ty *b = *(const message_ty **) vb;
296  int cmp;
297
298  /* No filepos is smaller than any other filepos.  */
299  if (a->filepos_count == 0)
300    {
301      if (b->filepos_count != 0)
302	return -1;
303    }
304  if (b->filepos_count == 0)
305    return 1;
306
307  /* Compare on the file names...  */
308  cmp = strcmp (a->filepos[0].file_name, b->filepos[0].file_name);
309  if (cmp != 0)
310    return cmp;
311
312  /* If they are equal, compare on the line numbers...  */
313  cmp = a->filepos[0].line_number - b->filepos[0].line_number;
314  if (cmp != 0)
315    return cmp;
316
317  /* If they are equal, compare on the msgid strings.  */
318  /* Because msgids normally contain only ASCII characters, it is OK to
319     sort them as if we were in the C locale. And strcoll() in the C locale
320     is the same as strcmp().  */
321  return strcmp (a->msgid, b->msgid);
322}
323
324
325void
326msgdomain_list_sort_by_filepos (msgdomain_list_ty *mdlp)
327{
328  size_t k;
329
330  /* It makes sense to compare filepos[0] of different messages only after
331     the filepos[] array of each message has been sorted.  Sort it now.  */
332  msgdomain_list_sort_filepos (mdlp);
333
334  for (k = 0; k < mdlp->nitems; k++)
335    {
336      message_list_ty *mlp = mdlp->item[k]->messages;
337
338      if (mlp->nitems > 0)
339	qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_filepos);
340    }
341}
342