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.  */
47 static size_t page_width = PAGE_WIDTH;
48 
49 void
message_page_width_set(size_t n)50 message_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 
68 void
msgdomain_list_print(msgdomain_list_ty * mdlp,const char * filename,catalog_output_format_ty output_syntax,bool force,bool debug)69 msgdomain_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, _("\
103 Cannot 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, _("\
106 Cannot 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, _("\
139 message 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, _("\
174 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."));
175 	      else
176 		po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
177 			   has_plural->file_name, has_plural->line_number,
178 			   (size_t)(-1), false, _("\
179 message 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 
225 static int
cmp_by_msgid(const void * va,const void * vb)226 cmp_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 
237 void
msgdomain_list_sort_by_msgid(msgdomain_list_ty * mdlp)238 msgdomain_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 
254 static int
cmp_filepos(const void * va,const void * vb)255 cmp_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 
268 static void
msgdomain_list_sort_filepos(msgdomain_list_ty * mdlp)269 msgdomain_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 
291 static int
cmp_by_filepos(const void * va,const void * vb)292 cmp_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 
325 void
msgdomain_list_sort_by_filepos(msgdomain_list_ty * mdlp)326 msgdomain_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