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