1/* GNU gettext - internationalization aids 2 Copyright (C) 1995-1998, 2000-2007 Free Software Foundation, Inc. 3 4 This file was written by Peter Miller <millerp@canb.auug.org.au> 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. */ 18 19#ifdef HAVE_CONFIG_H 20# include <config.h> 21#endif 22#include <alloca.h> 23 24/* Specification. */ 25#include "write-po.h" 26 27#include <errno.h> 28#include <limits.h> 29#include <stdio.h> 30#include <stdlib.h> 31#include <string.h> 32 33#if HAVE_ICONV 34# include <iconv.h> 35#endif 36 37#include "c-ctype.h" 38#include "po-charset.h" 39#include "format.h" 40#include "linebreak.h" 41#include "msgl-ascii.h" 42#include "write-catalog.h" 43#include "xalloc.h" 44#include "xmalloca.h" 45#include "c-strstr.h" 46#include "ostream.h" 47#ifdef GETTEXTDATADIR 48# include "styled-ostream.h" 49#endif 50#include "xvasprintf.h" 51#include "po-xerror.h" 52#include "gettext.h" 53 54/* Our regular abbreviation. */ 55#define _(str) gettext (str) 56 57#if HAVE_DECL_PUTC_UNLOCKED 58# undef putc 59# define putc putc_unlocked 60#endif 61 62 63/* =================== Putting together a #, flags line. =================== */ 64 65 66/* Convert IS_FORMAT in the context of programming language LANG to a flag 67 string for use in #, flags. */ 68 69const char * 70make_format_description_string (enum is_format is_format, const char *lang, 71 bool debug) 72{ 73 static char result[100]; 74 75 switch (is_format) 76 { 77 case possible: 78 if (debug) 79 { 80 sprintf (result, "possible-%s-format", lang); 81 break; 82 } 83 /* FALLTHROUGH */ 84 case yes_according_to_context: 85 case yes: 86 sprintf (result, "%s-format", lang); 87 break; 88 case no: 89 sprintf (result, "no-%s-format", lang); 90 break; 91 default: 92 /* The others have already been filtered out by significant_format_p. */ 93 abort (); 94 } 95 96 return result; 97} 98 99 100/* Return true if IS_FORMAT is worth mentioning in a #, flags list. */ 101 102bool 103significant_format_p (enum is_format is_format) 104{ 105 return is_format != undecided && is_format != impossible; 106} 107 108 109/* Return true if one of IS_FORMAT is worth mentioning in a #, flags list. */ 110 111static bool 112has_significant_format_p (const enum is_format is_format[NFORMATS]) 113{ 114 size_t i; 115 116 for (i = 0; i < NFORMATS; i++) 117 if (significant_format_p (is_format[i])) 118 return true; 119 return false; 120} 121 122 123/* Convert a wrapping flag DO_WRAP to a string for use in #, flags. */ 124 125static const char * 126make_c_width_description_string (enum is_wrap do_wrap) 127{ 128 const char *result = NULL; 129 130 switch (do_wrap) 131 { 132 case yes: 133 result = "wrap"; 134 break; 135 case no: 136 result = "no-wrap"; 137 break; 138 default: 139 abort (); 140 } 141 142 return result; 143} 144 145 146/* ========================== Styling primitives. ========================== */ 147 148 149/* When compiled in src, enable styling support. 150 When compiled in libgettextpo, don't enable styling support. */ 151#ifdef GETTEXTDATADIR 152 153/* Return true if the stream is an instance of styled_ostream_t. */ 154static inline bool 155is_stylable (ostream_t stream) 156{ 157 return IS_INSTANCE (stream, ostream, styled_ostream); 158} 159 160/* Start a run of text belonging to a given CSS class. */ 161static void 162begin_css_class (ostream_t stream, const char *classname) 163{ 164 if (is_stylable (stream)) 165 styled_ostream_begin_use_class ((styled_ostream_t) stream, classname); 166} 167 168/* End a run of text belonging to a given CSS class. */ 169static void 170end_css_class (ostream_t stream, const char *classname) 171{ 172 if (is_stylable (stream)) 173 styled_ostream_end_use_class ((styled_ostream_t) stream, classname); 174} 175 176#else 177 178#define is_stylable(stream) false 179#define begin_css_class(stream,classname) /* empty */ 180#define end_css_class(stream,classname) /* empty */ 181 182#endif 183 184/* CSS classes at message level. */ 185static const char class_header[] = "header"; 186static const char class_translated[] = "translated"; 187static const char class_untranslated[] = "untranslated"; 188static const char class_fuzzy[] = "fuzzy"; 189static const char class_obsolete[] = "obsolete"; 190 191/* CSS classes describing the parts of a message. */ 192static const char class_comment[] = "comment"; 193static const char class_translator_comment[] = "translator-comment"; 194static const char class_extracted_comment[] = "extracted-comment"; 195static const char class_reference_comment[] = "reference-comment"; 196static const char class_reference[] = "reference"; 197static const char class_flag_comment[] = "flag-comment"; 198static const char class_flag[] = "flag"; 199static const char class_fuzzy_flag[] = "fuzzy-flag"; 200static const char class_previous_comment[] = "previous-comment"; 201static const char class_previous[] = "previous"; 202static const char class_msgid[] = "msgid"; 203static const char class_msgstr[] = "msgstr"; 204static const char class_keyword[] = "keyword"; 205static const char class_string[] = "string"; 206 207/* CSS classes for the contents of strings. */ 208static const char class_text[] = "text"; 209static const char class_escape_sequence[] = "escape-sequence"; 210static const char class_format_directive[] = "format-directive"; 211static const char class_invalid_format_directive[] = "invalid-format-directive"; 212#if 0 213static const char class_added[] = "added"; 214static const char class_changed[] = "changed"; 215static const char class_removed[] = "removed"; 216#endif 217 218/* Per-character attributes. */ 219enum 220{ 221 ATTR_ESCAPE_SEQUENCE = 1 << 0, 222 /* The following two are exclusive. */ 223 ATTR_FORMAT_DIRECTIVE = 1 << 1, 224 ATTR_INVALID_FORMAT_DIRECTIVE = 1 << 2 225}; 226 227 228/* ================ Output parts of a message, as comments. ================ */ 229 230 231/* Output mp->comment as a set of comment lines. */ 232 233void 234message_print_comment (const message_ty *mp, ostream_t stream) 235{ 236 if (mp->comment != NULL) 237 { 238 size_t j; 239 240 begin_css_class (stream, class_translator_comment); 241 242 for (j = 0; j < mp->comment->nitems; ++j) 243 { 244 const char *s = mp->comment->item[j]; 245 do 246 { 247 const char *e; 248 ostream_write_str (stream, "#"); 249 if (*s != '\0') 250 ostream_write_str (stream, " "); 251 e = strchr (s, '\n'); 252 if (e == NULL) 253 { 254 ostream_write_str (stream, s); 255 s = NULL; 256 } 257 else 258 { 259 ostream_write_mem (stream, s, e - s); 260 s = e + 1; 261 } 262 ostream_write_str (stream, "\n"); 263 } 264 while (s != NULL); 265 } 266 267 end_css_class (stream, class_translator_comment); 268 } 269} 270 271 272/* Output mp->comment_dot as a set of comment lines. */ 273 274void 275message_print_comment_dot (const message_ty *mp, ostream_t stream) 276{ 277 if (mp->comment_dot != NULL) 278 { 279 size_t j; 280 281 begin_css_class (stream, class_extracted_comment); 282 283 for (j = 0; j < mp->comment_dot->nitems; ++j) 284 { 285 const char *s = mp->comment_dot->item[j]; 286 ostream_write_str (stream, "#."); 287 if (*s != '\0') 288 ostream_write_str (stream, " "); 289 ostream_write_str (stream, s); 290 ostream_write_str (stream, "\n"); 291 } 292 293 end_css_class (stream, class_extracted_comment); 294 } 295} 296 297 298/* Output mp->filepos as a set of comment lines. */ 299 300void 301message_print_comment_filepos (const message_ty *mp, ostream_t stream, 302 bool uniforum, size_t page_width) 303{ 304 if (mp->filepos_count != 0) 305 { 306 begin_css_class (stream, class_reference_comment); 307 308 if (uniforum) 309 { 310 size_t j; 311 312 for (j = 0; j < mp->filepos_count; ++j) 313 { 314 lex_pos_ty *pp = &mp->filepos[j]; 315 char *cp = pp->file_name; 316 char *str; 317 318 while (cp[0] == '.' && cp[1] == '/') 319 cp += 2; 320 ostream_write_str (stream, "# "); 321 begin_css_class (stream, class_reference); 322 /* There are two Sun formats to choose from: SunOS and 323 Solaris. Use the Solaris form here. */ 324 str = xasprintf ("File: %s, line: %ld", 325 cp, (long) pp->line_number); 326 ostream_write_str (stream, str); 327 end_css_class (stream, class_reference); 328 ostream_write_str (stream, "\n"); 329 free (str); 330 } 331 } 332 else 333 { 334 size_t column; 335 size_t j; 336 337 ostream_write_str (stream, "#:"); 338 column = 2; 339 for (j = 0; j < mp->filepos_count; ++j) 340 { 341 lex_pos_ty *pp; 342 char buffer[21]; 343 char *cp; 344 size_t len; 345 346 pp = &mp->filepos[j]; 347 cp = pp->file_name; 348 while (cp[0] == '.' && cp[1] == '/') 349 cp += 2; 350 /* Some xgettext input formats, like RST, lack line numbers. */ 351 if (pp->line_number == (size_t)(-1)) 352 buffer[0] = '\0'; 353 else 354 sprintf (buffer, ":%ld", (long) pp->line_number); 355 len = strlen (cp) + strlen (buffer) + 1; 356 if (column > 2 && column + len >= page_width) 357 { 358 ostream_write_str (stream, "\n#:"); 359 column = 2; 360 } 361 ostream_write_str (stream, " "); 362 begin_css_class (stream, class_reference); 363 ostream_write_str (stream, cp); 364 ostream_write_str (stream, buffer); 365 end_css_class (stream, class_reference); 366 column += len; 367 } 368 ostream_write_str (stream, "\n"); 369 } 370 371 end_css_class (stream, class_reference_comment); 372 } 373} 374 375 376/* Output mp->is_fuzzy, mp->is_format, mp->do_wrap as a comment line. */ 377 378void 379message_print_comment_flags (const message_ty *mp, ostream_t stream, bool debug) 380{ 381 if ((mp->is_fuzzy && mp->msgstr[0] != '\0') 382 || has_significant_format_p (mp->is_format) 383 || mp->do_wrap == no) 384 { 385 bool first_flag = true; 386 size_t i; 387 388 begin_css_class (stream, class_flag_comment); 389 390 ostream_write_str (stream, "#,"); 391 392 /* We don't print the fuzzy flag if the msgstr is empty. This 393 might be introduced by the user but we want to normalize the 394 output. */ 395 if (mp->is_fuzzy && mp->msgstr[0] != '\0') 396 { 397 ostream_write_str (stream, " "); 398 begin_css_class (stream, class_flag); 399 begin_css_class (stream, class_fuzzy_flag); 400 ostream_write_str (stream, "fuzzy"); 401 end_css_class (stream, class_fuzzy_flag); 402 end_css_class (stream, class_flag); 403 first_flag = false; 404 } 405 406 for (i = 0; i < NFORMATS; i++) 407 if (significant_format_p (mp->is_format[i])) 408 { 409 if (!first_flag) 410 ostream_write_str (stream, ","); 411 412 ostream_write_str (stream, " "); 413 begin_css_class (stream, class_flag); 414 ostream_write_str (stream, 415 make_format_description_string (mp->is_format[i], 416 format_language[i], 417 debug)); 418 end_css_class (stream, class_flag); 419 first_flag = false; 420 } 421 422 if (mp->do_wrap == no) 423 { 424 if (!first_flag) 425 ostream_write_str (stream, ","); 426 427 ostream_write_str (stream, " "); 428 begin_css_class (stream, class_flag); 429 ostream_write_str (stream, 430 make_c_width_description_string (mp->do_wrap)); 431 end_css_class (stream, class_flag); 432 first_flag = false; 433 } 434 435 ostream_write_str (stream, "\n"); 436 437 end_css_class (stream, class_flag_comment); 438 } 439} 440 441 442/* ========= Some parameters for use by 'msgdomain_list_print_po'. ========= */ 443 444 445/* This variable controls the extent to which the page width applies. 446 True means it applies to message strings and file reference lines. 447 False means it applies to file reference lines only. */ 448static bool wrap_strings = true; 449 450void 451message_page_width_ignore () 452{ 453 wrap_strings = false; 454} 455 456 457/* These three variables control the output style of the message_print 458 function. Interface functions for them are to be used. */ 459static bool indent = false; 460static bool uniforum = false; 461static bool escape = false; 462 463void 464message_print_style_indent () 465{ 466 indent = true; 467} 468 469void 470message_print_style_uniforum () 471{ 472 uniforum = true; 473} 474 475void 476message_print_style_escape (bool flag) 477{ 478 escape = flag; 479} 480 481 482/* =============== msgdomain_list_print_po() and subroutines. =============== */ 483 484 485/* A version of memcpy optimized for the case n <= 1. */ 486static inline void 487memcpy_small (void *dst, const void *src, size_t n) 488{ 489 if (n > 0) 490 { 491 char *q = (char *) dst; 492 const char *p = (const char *) src; 493 494 *q = *p; 495 if (--n > 0) 496 do *++q = *++p; while (--n > 0); 497 } 498} 499 500 501/* A version of memset optimized for the case n <= 1. */ 502static inline void 503memset_small (void *dst, char c, size_t n) 504{ 505 if (n > 0) 506 { 507 char *p = (char *) dst; 508 509 *p = c; 510 if (--n > 0) 511 do *++p = c; while (--n > 0); 512 } 513} 514 515 516static void 517wrap (const message_ty *mp, ostream_t stream, 518 const char *line_prefix, int extra_indent, const char *css_class, 519 const char *name, const char *value, 520 enum is_wrap do_wrap, size_t page_width, 521 const char *charset) 522{ 523 const char *canon_charset; 524 char *fmtdir; 525 const char *s; 526 bool first_line; 527#if HAVE_ICONV 528 const char *envval; 529 iconv_t conv; 530#endif 531 bool weird_cjk; 532 533 canon_charset = po_charset_canonicalize (charset); 534 535#if HAVE_ICONV 536 /* The old Solaris/openwin msgfmt and GNU msgfmt <= 0.10.35 don't know 537 about multibyte encodings, and require a spurious backslash after 538 every multibyte character whose last byte is 0x5C. Some programs, 539 like vim, distribute PO files in this broken format. It is important 540 for such programs that GNU msgmerge continues to support this old 541 PO file format when the Makefile requests it. */ 542 envval = getenv ("OLD_PO_FILE_OUTPUT"); 543 if (envval != NULL && *envval != '\0') 544 /* Write a PO file in old format, with extraneous backslashes. */ 545 conv = (iconv_t)(-1); 546 else 547 if (canon_charset == NULL) 548 /* Invalid PO file encoding. */ 549 conv = (iconv_t)(-1); 550 else 551 /* Avoid glibc-2.1 bug with EUC-KR. */ 552# if (__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) && !defined _LIBICONV_VERSION 553 if (strcmp (canon_charset, "EUC-KR") == 0) 554 conv = (iconv_t)(-1); 555 else 556# endif 557 /* Avoid Solaris 2.9 bug with GB2312, EUC-TW, BIG5, BIG5-HKSCS, GBK, 558 GB18030. */ 559# if defined __sun && !defined _LIBICONV_VERSION 560 if ( strcmp (canon_charset, "GB2312") == 0 561 || strcmp (canon_charset, "EUC-TW") == 0 562 || strcmp (canon_charset, "BIG5") == 0 563 || strcmp (canon_charset, "BIG5-HKSCS") == 0 564 || strcmp (canon_charset, "GBK") == 0 565 || strcmp (canon_charset, "GB18030") == 0) 566 conv = (iconv_t)(-1); 567 else 568# endif 569 /* Use iconv() to parse multibyte characters. */ 570 conv = iconv_open ("UTF-8", canon_charset); 571 572 if (conv != (iconv_t)(-1)) 573 weird_cjk = false; 574 else 575#endif 576 if (canon_charset == NULL) 577 weird_cjk = false; 578 else 579 weird_cjk = po_is_charset_weird_cjk (canon_charset); 580 581 if (canon_charset == NULL) 582 canon_charset = po_charset_ascii; 583 584 /* Determine the extent of format string directives. */ 585 fmtdir = NULL; 586 if (is_stylable (stream) && value[0] != '\0') 587 { 588 bool is_msgstr = 589 (strlen (name) >= 6 && memcmp (name, "msgstr", 6) == 0); 590 /* or equivalent: = (css_class == class_msgstr) */ 591 size_t i; 592 593 for (i = 0; i < NFORMATS; i++) 594 if (possible_format_p (mp->is_format[i])) 595 { 596 size_t len = strlen (value); 597 struct formatstring_parser *parser = formatstring_parsers[i]; 598 char *invalid_reason = NULL; 599 void *descr; 600 char *fdp; 601 char *fd_end; 602 603 fmtdir = XCALLOC (len, char); 604 descr = parser->parse (value, is_msgstr, fmtdir, &invalid_reason); 605 if (descr != NULL) 606 parser->free (descr); 607 608 /* Locate the FMTDIR_* bits and transform the array to an array 609 of attributes. */ 610 fd_end = fmtdir + len; 611 for (fdp = fmtdir; fdp < fd_end; fdp++) 612 if (*fdp & FMTDIR_START) 613 { 614 char *fdq; 615 for (fdq = fdp; fdq < fd_end; fdq++) 616 if (*fdq & (FMTDIR_END | FMTDIR_ERROR)) 617 break; 618 if (!(fdq < fd_end)) 619 /* The ->parse method has determined the start of a 620 formatstring directive but not stored a bit indicating 621 its end. It is a bug in the ->parse method. */ 622 abort (); 623 if (*fdq & FMTDIR_ERROR) 624 memset (fdp, ATTR_INVALID_FORMAT_DIRECTIVE, fdq - fdp + 1); 625 else 626 memset (fdp, ATTR_FORMAT_DIRECTIVE, fdq - fdp + 1); 627 fdp = fdq; 628 } 629 else 630 *fdp = 0; 631 632 break; 633 } 634 } 635 636 /* Loop over the '\n' delimited portions of value. */ 637 s = value; 638 first_line = true; 639 do 640 { 641 /* The usual escapes, as defined by the ANSI C Standard. */ 642# define is_escape(c) \ 643 ((c) == '\a' || (c) == '\b' || (c) == '\f' || (c) == '\n' \ 644 || (c) == '\r' || (c) == '\t' || (c) == '\v') 645 646 const char *es; 647 const char *ep; 648 size_t portion_len; 649 char *portion; 650 char *overrides; 651 char *attributes; 652 char *linebreaks; 653 char *pp; 654 char *op; 655 char *ap; 656 int startcol, startcol_after_break, width; 657 size_t i; 658 659 for (es = s; *es != '\0'; ) 660 if (*es++ == '\n') 661 break; 662 663 /* Expand escape sequences in each portion. */ 664 for (ep = s, portion_len = 0; ep < es; ep++) 665 { 666 char c = *ep; 667 if (is_escape (c)) 668 portion_len += 2; 669 else if (escape && !c_isprint ((unsigned char) c)) 670 portion_len += 4; 671 else if (c == '\\' || c == '"') 672 portion_len += 2; 673 else 674 { 675#if HAVE_ICONV 676 if (conv != (iconv_t)(-1)) 677 { 678 /* Skip over a complete multi-byte character. Don't 679 interpret the second byte of a multi-byte character as 680 ASCII. This is needed for the BIG5, BIG5-HKSCS, GBK, 681 GB18030, SHIFT_JIS, JOHAB encodings. */ 682 char scratchbuf[64]; 683 const char *inptr = ep; 684 size_t insize; 685 char *outptr = &scratchbuf[0]; 686 size_t outsize = sizeof (scratchbuf); 687 size_t res; 688 689 res = (size_t)(-1); 690 for (insize = 1; inptr + insize <= es; insize++) 691 { 692 res = iconv (conv, 693 (ICONV_CONST char **) &inptr, &insize, 694 &outptr, &outsize); 695 if (!(res == (size_t)(-1) && errno == EINVAL)) 696 break; 697 /* We expect that no input bytes have been consumed 698 so far. */ 699 if (inptr != ep) 700 abort (); 701 } 702 if (res == (size_t)(-1)) 703 { 704 if (errno == EILSEQ) 705 { 706 po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false, 707 _("invalid multibyte sequence")); 708 continue; 709 } 710 else 711 abort (); 712 } 713 insize = inptr - ep; 714 portion_len += insize; 715 ep += insize - 1; 716 } 717 else 718#endif 719 { 720 if (weird_cjk 721 /* Special handling of encodings with CJK structure. */ 722 && ep + 2 <= es 723 && (unsigned char) ep[0] >= 0x80 724 && (unsigned char) ep[1] >= 0x30) 725 { 726 portion_len += 2; 727 ep += 1; 728 } 729 else 730 portion_len += 1; 731 } 732 } 733 } 734 portion = XNMALLOC (portion_len, char); 735 overrides = XNMALLOC (portion_len, char); 736 memset (overrides, UC_BREAK_UNDEFINED, portion_len); 737 attributes = XNMALLOC (portion_len, char); 738 for (ep = s, pp = portion, op = overrides, ap = attributes; ep < es; ep++) 739 { 740 char c = *ep; 741 char attr = (fmtdir != NULL ? fmtdir[ep - value] : 0); 742 if (is_escape (c)) 743 { 744 switch (c) 745 { 746 case '\a': c = 'a'; break; 747 case '\b': c = 'b'; break; 748 case '\f': c = 'f'; break; 749 case '\n': c = 'n'; break; 750 case '\r': c = 'r'; break; 751 case '\t': c = 't'; break; 752 case '\v': c = 'v'; break; 753 default: abort (); 754 } 755 *pp++ = '\\'; 756 *pp++ = c; 757 op++; 758 *op++ = UC_BREAK_PROHIBITED; 759 *ap++ = attr | ATTR_ESCAPE_SEQUENCE; 760 *ap++ = attr | ATTR_ESCAPE_SEQUENCE; 761 /* We warn about any use of escape sequences beside 762 '\n' and '\t'. */ 763 if (c != 'n' && c != 't') 764 { 765 char *error_message = 766 xasprintf (_("\ 767internationalized messages should not contain the `\\%c' escape sequence"), 768 c); 769 po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false, 770 error_message); 771 free (error_message); 772 } 773 } 774 else if (escape && !c_isprint ((unsigned char) c)) 775 { 776 *pp++ = '\\'; 777 *pp++ = '0' + (((unsigned char) c >> 6) & 7); 778 *pp++ = '0' + (((unsigned char) c >> 3) & 7); 779 *pp++ = '0' + ((unsigned char) c & 7); 780 op++; 781 *op++ = UC_BREAK_PROHIBITED; 782 *op++ = UC_BREAK_PROHIBITED; 783 *op++ = UC_BREAK_PROHIBITED; 784 *ap++ = attr | ATTR_ESCAPE_SEQUENCE; 785 *ap++ = attr | ATTR_ESCAPE_SEQUENCE; 786 *ap++ = attr | ATTR_ESCAPE_SEQUENCE; 787 *ap++ = attr | ATTR_ESCAPE_SEQUENCE; 788 } 789 else if (c == '\\' || c == '"') 790 { 791 *pp++ = '\\'; 792 *pp++ = c; 793 op++; 794 *op++ = UC_BREAK_PROHIBITED; 795 *ap++ = attr | ATTR_ESCAPE_SEQUENCE; 796 *ap++ = attr | ATTR_ESCAPE_SEQUENCE; 797 } 798 else 799 { 800#if HAVE_ICONV 801 if (conv != (iconv_t)(-1)) 802 { 803 /* Copy a complete multi-byte character. Don't 804 interpret the second byte of a multi-byte character as 805 ASCII. This is needed for the BIG5, BIG5-HKSCS, GBK, 806 GB18030, SHIFT_JIS, JOHAB encodings. */ 807 char scratchbuf[64]; 808 const char *inptr = ep; 809 size_t insize; 810 char *outptr = &scratchbuf[0]; 811 size_t outsize = sizeof (scratchbuf); 812 size_t res; 813 814 res = (size_t)(-1); 815 for (insize = 1; inptr + insize <= es; insize++) 816 { 817 res = iconv (conv, 818 (ICONV_CONST char **) &inptr, &insize, 819 &outptr, &outsize); 820 if (!(res == (size_t)(-1) && errno == EINVAL)) 821 break; 822 /* We expect that no input bytes have been consumed 823 so far. */ 824 if (inptr != ep) 825 abort (); 826 } 827 if (res == (size_t)(-1)) 828 { 829 if (errno == EILSEQ) 830 { 831 po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, 832 false, _("invalid multibyte sequence")); 833 continue; 834 } 835 else 836 abort (); 837 } 838 insize = inptr - ep; 839 memcpy_small (pp, ep, insize); 840 pp += insize; 841 op += insize; 842 memset_small (ap, attr, insize); 843 ap += insize; 844 ep += insize - 1; 845 } 846 else 847#endif 848 { 849 if (weird_cjk 850 /* Special handling of encodings with CJK structure. */ 851 && ep + 2 <= es 852 && (unsigned char) c >= 0x80 853 && (unsigned char) ep[1] >= 0x30) 854 { 855 *pp++ = c; 856 ep += 1; 857 *pp++ = *ep; 858 op += 2; 859 *ap++ = attr; 860 *ap++ = attr; 861 } 862 else 863 { 864 *pp++ = c; 865 op++; 866 *ap++ = attr; 867 } 868 } 869 } 870 } 871 872 /* Don't break immediately before the "\n" at the end. */ 873 if (es > s && es[-1] == '\n') 874 overrides[portion_len - 2] = UC_BREAK_PROHIBITED; 875 876 linebreaks = XNMALLOC (portion_len, char); 877 878 /* Subsequent lines after a break are all indented. 879 See INDENT-S. */ 880 startcol_after_break = (line_prefix ? strlen (line_prefix) : 0); 881 if (indent) 882 startcol_after_break = (startcol_after_break + extra_indent + 8) & ~7; 883 startcol_after_break++; 884 885 /* The line width. Allow room for the closing quote character. */ 886 width = (wrap_strings && do_wrap != no ? page_width : INT_MAX) - 1; 887 /* Adjust for indentation of subsequent lines. */ 888 width -= startcol_after_break; 889 890 recompute: 891 /* The line starts with different things depending on whether it 892 is the first line, and if we are using the indented style. 893 See INDENT-F. */ 894 startcol = (line_prefix ? strlen (line_prefix) : 0); 895 if (first_line) 896 { 897 startcol += strlen (name); 898 if (indent) 899 startcol = (startcol + extra_indent + 8) & ~7; 900 else 901 startcol++; 902 } 903 else 904 { 905 if (indent) 906 startcol = (startcol + extra_indent + 8) & ~7; 907 } 908 /* Allow room for the opening quote character. */ 909 startcol++; 910 /* Adjust for indentation of subsequent lines. */ 911 startcol -= startcol_after_break; 912 913 /* Do line breaking on the portion. */ 914 mbs_width_linebreaks (portion, portion_len, width, startcol, 0, 915 overrides, canon_charset, linebreaks); 916 917 /* If this is the first line, and we are not using the indented 918 style, and the line would wrap, then use an empty first line 919 and restart. */ 920 if (first_line && !indent 921 && portion_len > 0 922 && (*es != '\0' 923 || startcol > width 924 || memchr (linebreaks, UC_BREAK_POSSIBLE, portion_len) != NULL)) 925 { 926 if (line_prefix != NULL) 927 ostream_write_str (stream, line_prefix); 928 begin_css_class (stream, css_class); 929 begin_css_class (stream, class_keyword); 930 ostream_write_str (stream, name); 931 end_css_class (stream, class_keyword); 932 ostream_write_str (stream, " "); 933 begin_css_class (stream, class_string); 934 ostream_write_str (stream, "\"\""); 935 end_css_class (stream, class_string); 936 end_css_class (stream, css_class); 937 ostream_write_str (stream, "\n"); 938 first_line = false; 939 /* Recompute startcol and linebreaks. */ 940 goto recompute; 941 } 942 943 /* Print the beginning of the line. This will depend on whether 944 this is the first line, and if the indented style is being 945 used. INDENT-F. */ 946 { 947 int currcol = 0; 948 949 if (line_prefix != NULL) 950 { 951 ostream_write_str (stream, line_prefix); 952 currcol = strlen (line_prefix); 953 } 954 begin_css_class (stream, css_class); 955 if (first_line) 956 { 957 begin_css_class (stream, class_keyword); 958 ostream_write_str (stream, name); 959 currcol += strlen (name); 960 end_css_class (stream, class_keyword); 961 if (indent) 962 { 963 if (extra_indent > 0) 964 ostream_write_mem (stream, " ", extra_indent); 965 currcol += extra_indent; 966 ostream_write_mem (stream, " ", 8 - (currcol & 7)); 967 currcol = (currcol + 8) & ~7; 968 } 969 else 970 { 971 ostream_write_str (stream, " "); 972 currcol++; 973 } 974 first_line = false; 975 } 976 else 977 { 978 if (indent) 979 { 980 if (extra_indent > 0) 981 ostream_write_mem (stream, " ", extra_indent); 982 currcol += extra_indent; 983 ostream_write_mem (stream, " ", 8 - (currcol & 7)); 984 currcol = (currcol + 8) & ~7; 985 } 986 } 987 } 988 989 /* Print the portion itself, with linebreaks where necessary. */ 990 { 991 char currattr = 0; 992 993 begin_css_class (stream, class_string); 994 ostream_write_str (stream, "\""); 995 begin_css_class (stream, class_text); 996 997 for (i = 0; i < portion_len; i++) 998 { 999 if (linebreaks[i] == UC_BREAK_POSSIBLE) 1000 { 1001 int currcol; 1002 1003 /* Change currattr so that it becomes 0. */ 1004 if (currattr & ATTR_ESCAPE_SEQUENCE) 1005 { 1006 end_css_class (stream, class_escape_sequence); 1007 currattr &= ~ATTR_ESCAPE_SEQUENCE; 1008 } 1009 if (currattr & ATTR_FORMAT_DIRECTIVE) 1010 { 1011 end_css_class (stream, class_format_directive); 1012 currattr &= ~ATTR_FORMAT_DIRECTIVE; 1013 } 1014 else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE) 1015 { 1016 end_css_class (stream, class_invalid_format_directive); 1017 currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE; 1018 } 1019 if (!(currattr == 0)) 1020 abort (); 1021 1022 end_css_class (stream, class_text); 1023 ostream_write_str (stream, "\""); 1024 end_css_class (stream, class_string); 1025 end_css_class (stream, css_class); 1026 ostream_write_str (stream, "\n"); 1027 currcol = 0; 1028 /* INDENT-S. */ 1029 if (line_prefix != NULL) 1030 { 1031 ostream_write_str (stream, line_prefix); 1032 currcol = strlen (line_prefix); 1033 } 1034 begin_css_class (stream, css_class); 1035 if (indent) 1036 { 1037 ostream_write_mem (stream, " ", 8 - (currcol & 7)); 1038 currcol = (currcol + 8) & ~7; 1039 } 1040 begin_css_class (stream, class_string); 1041 ostream_write_str (stream, "\""); 1042 begin_css_class (stream, class_text); 1043 } 1044 /* Change currattr so that it matches attributes[i]. */ 1045 if (attributes[i] != currattr) 1046 { 1047 /* class_escape_sequence occurs inside class_format_directive 1048 and class_invalid_format_directive, so clear it first. */ 1049 if (currattr & ATTR_ESCAPE_SEQUENCE) 1050 { 1051 end_css_class (stream, class_escape_sequence); 1052 currattr &= ~ATTR_ESCAPE_SEQUENCE; 1053 } 1054 if (~attributes[i] & currattr & ATTR_FORMAT_DIRECTIVE) 1055 { 1056 end_css_class (stream, class_format_directive); 1057 currattr &= ~ATTR_FORMAT_DIRECTIVE; 1058 } 1059 else if (~attributes[i] & currattr & ATTR_INVALID_FORMAT_DIRECTIVE) 1060 { 1061 end_css_class (stream, class_invalid_format_directive); 1062 currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE; 1063 } 1064 if (attributes[i] & ~currattr & ATTR_FORMAT_DIRECTIVE) 1065 { 1066 begin_css_class (stream, class_format_directive); 1067 currattr |= ATTR_FORMAT_DIRECTIVE; 1068 } 1069 else if (attributes[i] & ~currattr & ATTR_INVALID_FORMAT_DIRECTIVE) 1070 { 1071 begin_css_class (stream, class_invalid_format_directive); 1072 currattr |= ATTR_INVALID_FORMAT_DIRECTIVE; 1073 } 1074 /* class_escape_sequence occurs inside class_format_directive 1075 and class_invalid_format_directive, so set it last. */ 1076 if (attributes[i] & ~currattr & ATTR_ESCAPE_SEQUENCE) 1077 { 1078 begin_css_class (stream, class_escape_sequence); 1079 currattr |= ATTR_ESCAPE_SEQUENCE; 1080 } 1081 } 1082 ostream_write_mem (stream, &portion[i], 1); 1083 } 1084 1085 /* Change currattr so that it becomes 0. */ 1086 if (currattr & ATTR_ESCAPE_SEQUENCE) 1087 { 1088 end_css_class (stream, class_escape_sequence); 1089 currattr &= ~ATTR_ESCAPE_SEQUENCE; 1090 } 1091 if (currattr & ATTR_FORMAT_DIRECTIVE) 1092 { 1093 end_css_class (stream, class_format_directive); 1094 currattr &= ~ATTR_FORMAT_DIRECTIVE; 1095 } 1096 else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE) 1097 { 1098 end_css_class (stream, class_invalid_format_directive); 1099 currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE; 1100 } 1101 if (!(currattr == 0)) 1102 abort (); 1103 1104 end_css_class (stream, class_text); 1105 ostream_write_str (stream, "\""); 1106 end_css_class (stream, class_string); 1107 end_css_class (stream, css_class); 1108 ostream_write_str (stream, "\n"); 1109 } 1110 1111 free (linebreaks); 1112 free (attributes); 1113 free (overrides); 1114 free (portion); 1115 1116 s = es; 1117# undef is_escape 1118 } 1119 while (*s); 1120 1121 if (fmtdir != NULL) 1122 free (fmtdir); 1123 1124#if HAVE_ICONV 1125 if (conv != (iconv_t)(-1)) 1126 iconv_close (conv); 1127#endif 1128} 1129 1130 1131static void 1132print_blank_line (ostream_t stream) 1133{ 1134 if (uniforum) 1135 { 1136 begin_css_class (stream, class_comment); 1137 ostream_write_str (stream, "#\n"); 1138 end_css_class (stream, class_comment); 1139 } 1140 else 1141 ostream_write_str (stream, "\n"); 1142} 1143 1144 1145static void 1146message_print (const message_ty *mp, ostream_t stream, 1147 const char *charset, size_t page_width, bool blank_line, 1148 bool debug) 1149{ 1150 int extra_indent; 1151 1152 /* Separate messages with a blank line. Uniforum doesn't like blank 1153 lines, so use an empty comment (unless there already is one). */ 1154 if (blank_line && (!uniforum 1155 || mp->comment == NULL 1156 || mp->comment->nitems == 0 1157 || mp->comment->item[0][0] != '\0')) 1158 print_blank_line (stream); 1159 1160 if (is_header (mp)) 1161 begin_css_class (stream, class_header); 1162 else if (mp->msgstr[0] == '\0') 1163 begin_css_class (stream, class_untranslated); 1164 else if (mp->is_fuzzy) 1165 begin_css_class (stream, class_fuzzy); 1166 else 1167 begin_css_class (stream, class_translated); 1168 1169 begin_css_class (stream, class_comment); 1170 1171 /* Print translator comment if available. */ 1172 message_print_comment (mp, stream); 1173 1174 /* Print xgettext extracted comments. */ 1175 message_print_comment_dot (mp, stream); 1176 1177 /* Print the file position comments. This will help a human who is 1178 trying to navigate the sources. There is no problem of getting 1179 repeated positions, because duplicates are checked for. */ 1180 message_print_comment_filepos (mp, stream, uniforum, page_width); 1181 1182 /* Print flag information in special comment. */ 1183 message_print_comment_flags (mp, stream, debug); 1184 1185 /* Print the previous msgid. This helps the translator when the msgid has 1186 only slightly changed. */ 1187 begin_css_class (stream, class_previous_comment); 1188 if (mp->prev_msgctxt != NULL) 1189 wrap (mp, stream, "#| ", 0, class_previous, "msgctxt", mp->prev_msgctxt, 1190 mp->do_wrap, page_width, charset); 1191 if (mp->prev_msgid != NULL) 1192 wrap (mp, stream, "#| ", 0, class_previous, "msgid", mp->prev_msgid, 1193 mp->do_wrap, page_width, charset); 1194 if (mp->prev_msgid_plural != NULL) 1195 wrap (mp, stream, "#| ", 0, class_previous, "msgid_plural", 1196 mp->prev_msgid_plural, mp->do_wrap, page_width, charset); 1197 end_css_class (stream, class_previous_comment); 1198 extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL 1199 || mp->prev_msgid_plural != NULL 1200 ? 3 1201 : 0); 1202 1203 end_css_class (stream, class_comment); 1204 1205 /* Print each of the message components. Wrap them nicely so they 1206 are as readable as possible. If there is no recorded msgstr for 1207 this domain, emit an empty string. */ 1208 if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt) 1209 && po_charset_canonicalize (charset) != po_charset_utf8) 1210 { 1211 char *warning_message = 1212 xasprintf (_("\ 1213The following msgctxt contains non-ASCII characters.\n\ 1214This will cause problems to translators who use a character encoding\n\ 1215different from yours. Consider using a pure ASCII msgctxt instead.\n\ 1216%s\n"), mp->msgctxt); 1217 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message); 1218 free (warning_message); 1219 } 1220 if (!is_ascii_string (mp->msgid) 1221 && po_charset_canonicalize (charset) != po_charset_utf8) 1222 { 1223 char *warning_message = 1224 xasprintf (_("\ 1225The following msgid contains non-ASCII characters.\n\ 1226This will cause problems to translators who use a character encoding\n\ 1227different from yours. Consider using a pure ASCII msgid instead.\n\ 1228%s\n"), mp->msgid); 1229 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message); 1230 free (warning_message); 1231 } 1232 if (mp->msgctxt != NULL) 1233 wrap (mp, stream, NULL, extra_indent, class_msgid, "msgctxt", mp->msgctxt, 1234 mp->do_wrap, page_width, charset); 1235 wrap (mp, stream, NULL, extra_indent, class_msgid, "msgid", mp->msgid, 1236 mp->do_wrap, page_width, charset); 1237 if (mp->msgid_plural != NULL) 1238 wrap (mp, stream, NULL, extra_indent, class_msgid, "msgid_plural", 1239 mp->msgid_plural, mp->do_wrap, page_width, charset); 1240 1241 if (mp->msgid_plural == NULL) 1242 wrap (mp, stream, NULL, extra_indent, class_msgstr, "msgstr", mp->msgstr, 1243 mp->do_wrap, page_width, charset); 1244 else 1245 { 1246 char prefix_buf[20]; 1247 unsigned int i; 1248 const char *p; 1249 1250 for (p = mp->msgstr, i = 0; 1251 p < mp->msgstr + mp->msgstr_len; 1252 p += strlen (p) + 1, i++) 1253 { 1254 sprintf (prefix_buf, "msgstr[%u]", i); 1255 wrap (mp, stream, NULL, extra_indent, class_msgstr, prefix_buf, p, 1256 mp->do_wrap, page_width, charset); 1257 } 1258 } 1259 1260 if (is_header (mp)) 1261 end_css_class (stream, class_header); 1262 else if (mp->msgstr[0] == '\0') 1263 end_css_class (stream, class_untranslated); 1264 else if (mp->is_fuzzy) 1265 end_css_class (stream, class_fuzzy); 1266 else 1267 end_css_class (stream, class_translated); 1268} 1269 1270 1271static void 1272message_print_obsolete (const message_ty *mp, ostream_t stream, 1273 const char *charset, size_t page_width, bool blank_line) 1274{ 1275 int extra_indent; 1276 1277 /* If msgstr is the empty string we print nothing. */ 1278 if (mp->msgstr[0] == '\0') 1279 return; 1280 1281 /* Separate messages with a blank line. Uniforum doesn't like blank 1282 lines, so use an empty comment (unless there already is one). */ 1283 if (blank_line) 1284 print_blank_line (stream); 1285 1286 begin_css_class (stream, class_obsolete); 1287 1288 begin_css_class (stream, class_comment); 1289 1290 /* Print translator comment if available. */ 1291 message_print_comment (mp, stream); 1292 1293 /* Print xgettext extracted comments (normally empty). */ 1294 message_print_comment_dot (mp, stream); 1295 1296 /* Print the file position comments (normally empty). */ 1297 message_print_comment_filepos (mp, stream, uniforum, page_width); 1298 1299 /* Print flag information in special comment. */ 1300 if (mp->is_fuzzy) 1301 { 1302 bool first = true; 1303 1304 ostream_write_str (stream, "#,"); 1305 1306 if (mp->is_fuzzy) 1307 { 1308 ostream_write_str (stream, " fuzzy"); 1309 first = false; 1310 } 1311 1312 ostream_write_str (stream, "\n"); 1313 } 1314 1315 /* Print the previous msgid. This helps the translator when the msgid has 1316 only slightly changed. */ 1317 begin_css_class (stream, class_previous_comment); 1318 if (mp->prev_msgctxt != NULL) 1319 wrap (mp, stream, "#~| ", 0, class_previous, "msgctxt", mp->prev_msgctxt, 1320 mp->do_wrap, page_width, charset); 1321 if (mp->prev_msgid != NULL) 1322 wrap (mp, stream, "#~| ", 0, class_previous, "msgid", mp->prev_msgid, 1323 mp->do_wrap, page_width, charset); 1324 if (mp->prev_msgid_plural != NULL) 1325 wrap (mp, stream, "#~| ", 0, class_previous, "msgid_plural", 1326 mp->prev_msgid_plural, mp->do_wrap, page_width, charset); 1327 end_css_class (stream, class_previous_comment); 1328 extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL 1329 || mp->prev_msgid_plural != NULL 1330 ? 1 1331 : 0); 1332 1333 end_css_class (stream, class_comment); 1334 1335 /* Print each of the message components. Wrap them nicely so they 1336 are as readable as possible. */ 1337 if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt) 1338 && po_charset_canonicalize (charset) != po_charset_utf8) 1339 { 1340 char *warning_message = 1341 xasprintf (_("\ 1342The following msgctxt contains non-ASCII characters.\n\ 1343This will cause problems to translators who use a character encoding\n\ 1344different from yours. Consider using a pure ASCII msgctxt instead.\n\ 1345%s\n"), mp->msgctxt); 1346 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message); 1347 free (warning_message); 1348 } 1349 if (!is_ascii_string (mp->msgid) 1350 && po_charset_canonicalize (charset) != po_charset_utf8) 1351 { 1352 char *warning_message = 1353 xasprintf (_("\ 1354The following msgid contains non-ASCII characters.\n\ 1355This will cause problems to translators who use a character encoding\n\ 1356different from yours. Consider using a pure ASCII msgid instead.\n\ 1357%s\n"), mp->msgid); 1358 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message); 1359 free (warning_message); 1360 } 1361 if (mp->msgctxt != NULL) 1362 wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgctxt", mp->msgctxt, 1363 mp->do_wrap, page_width, charset); 1364 wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgid", mp->msgid, 1365 mp->do_wrap, page_width, charset); 1366 if (mp->msgid_plural != NULL) 1367 wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgid_plural", 1368 mp->msgid_plural, mp->do_wrap, page_width, charset); 1369 1370 if (mp->msgid_plural == NULL) 1371 wrap (mp, stream, "#~ ", extra_indent, class_msgstr, "msgstr", mp->msgstr, 1372 mp->do_wrap, page_width, charset); 1373 else 1374 { 1375 char prefix_buf[20]; 1376 unsigned int i; 1377 const char *p; 1378 1379 for (p = mp->msgstr, i = 0; 1380 p < mp->msgstr + mp->msgstr_len; 1381 p += strlen (p) + 1, i++) 1382 { 1383 sprintf (prefix_buf, "msgstr[%u]", i); 1384 wrap (mp, stream, "#~ ", extra_indent, class_msgstr, prefix_buf, p, 1385 mp->do_wrap, page_width, charset); 1386 } 1387 } 1388 1389 end_css_class (stream, class_obsolete); 1390} 1391 1392 1393static void 1394msgdomain_list_print_po (msgdomain_list_ty *mdlp, ostream_t stream, 1395 size_t page_width, bool debug) 1396{ 1397 size_t j, k; 1398 bool blank_line; 1399 1400 /* Write out the messages for each domain. */ 1401 blank_line = false; 1402 for (k = 0; k < mdlp->nitems; k++) 1403 { 1404 message_list_ty *mlp; 1405 const char *header; 1406 const char *charset; 1407 char *allocated_charset; 1408 1409 /* If the first domain is the default, don't bother emitting 1410 the domain name, because it is the default. */ 1411 if (!(k == 0 1412 && strcmp (mdlp->item[k]->domain, MESSAGE_DOMAIN_DEFAULT) == 0)) 1413 { 1414 if (blank_line) 1415 print_blank_line (stream); 1416 begin_css_class (stream, class_keyword); 1417 ostream_write_str (stream, "domain"); 1418 end_css_class (stream, class_keyword); 1419 ostream_write_str (stream, " "); 1420 begin_css_class (stream, class_string); 1421 ostream_write_str (stream, "\""); 1422 begin_css_class (stream, class_text); 1423 ostream_write_str (stream, mdlp->item[k]->domain); 1424 end_css_class (stream, class_text); 1425 ostream_write_str (stream, "\""); 1426 end_css_class (stream, class_string); 1427 ostream_write_str (stream, "\n"); 1428 blank_line = true; 1429 } 1430 1431 mlp = mdlp->item[k]->messages; 1432 1433 /* Search the header entry. */ 1434 header = NULL; 1435 for (j = 0; j < mlp->nitems; ++j) 1436 if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete) 1437 { 1438 header = mlp->item[j]->msgstr; 1439 break; 1440 } 1441 1442 /* Extract the charset name. */ 1443 charset = "ASCII"; 1444 allocated_charset = NULL; 1445 if (header != NULL) 1446 { 1447 const char *charsetstr = c_strstr (header, "charset="); 1448 1449 if (charsetstr != NULL) 1450 { 1451 size_t len; 1452 1453 charsetstr += strlen ("charset="); 1454 len = strcspn (charsetstr, " \t\n"); 1455 allocated_charset = (char *) xmalloca (len + 1); 1456 memcpy (allocated_charset, charsetstr, len); 1457 allocated_charset[len] = '\0'; 1458 charset = allocated_charset; 1459 1460 /* Treat the dummy default value as if it were absent. */ 1461 if (strcmp (charset, "CHARSET") == 0) 1462 charset = "ASCII"; 1463 } 1464 } 1465 1466 /* Write out each of the messages for this domain. */ 1467 for (j = 0; j < mlp->nitems; ++j) 1468 if (!mlp->item[j]->obsolete) 1469 { 1470 message_print (mlp->item[j], stream, charset, page_width, 1471 blank_line, debug); 1472 blank_line = true; 1473 } 1474 1475 /* Write out each of the obsolete messages for this domain. */ 1476 for (j = 0; j < mlp->nitems; ++j) 1477 if (mlp->item[j]->obsolete) 1478 { 1479 message_print_obsolete (mlp->item[j], stream, charset, page_width, 1480 blank_line); 1481 blank_line = true; 1482 } 1483 1484 if (allocated_charset != NULL) 1485 freea (allocated_charset); 1486 } 1487} 1488 1489 1490/* Describes a PO file in .po syntax. */ 1491const struct catalog_output_format output_format_po = 1492{ 1493 msgdomain_list_print_po, /* print */ 1494 false, /* requires_utf8 */ 1495 true, /* supports_color */ 1496 true, /* supports_multiple_domains */ 1497 true, /* supports_contexts */ 1498 true, /* supports_plurals */ 1499 false, /* alternative_is_po */ 1500 false /* alternative_is_java_class */ 1501}; 1502