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