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