1 /* GNU gettext - internationalization aids
2 Copyright (C) 1995-1998, 2000-2010, 2012, 2015 Free Software
5 This file was written by Peter Miller <millerp@canb.auug.org.au>
7 This program is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 3 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program. If not, see <http://www.gnu.org/licenses/>. */
39 #include "po-charset.h"
42 #include "msgl-ascii.h"
43 #include "write-catalog.h"
49 # include "styled-ostream.h"
51 #include "xvasprintf.h"
52 #include "po-xerror.h"
55 /* Our regular abbreviation. */
56 #define _(str) gettext (str)
58 #if HAVE_DECL_PUTC_UNLOCKED
60 # define putc putc_unlocked
64 /* =================== Putting together a #, flags line. =================== */
67 /* Convert IS_FORMAT in the context of programming language LANG to a flag
68 string for use in #, flags. */
71 make_format_description_string (enum is_format is_format, const char *lang,
74 static char result[100];
81 sprintf (result, "possible-%s-format", lang);
85 case yes_according_to_context:
87 sprintf (result, "%s-format", lang);
90 sprintf (result, "no-%s-format", lang);
93 /* The others have already been filtered out by significant_format_p. */
101 /* Return true if IS_FORMAT is worth mentioning in a #, flags list. */
104 significant_format_p (enum is_format is_format)
106 return is_format != undecided && is_format != impossible;
110 /* Return true if one of IS_FORMAT is worth mentioning in a #, flags list. */
113 has_significant_format_p (const enum is_format is_format[NFORMATS])
117 for (i = 0; i < NFORMATS; i++)
118 if (significant_format_p (is_format[i]))
124 /* Convert a RANGE to a freshly allocated string for use in #, flags. */
127 make_range_description_string (struct argument_range range)
129 return xasprintf ("range: %d..%d", range.min, range.max);
133 /* Convert a wrapping flag DO_WRAP to a string for use in #, flags. */
136 make_c_width_description_string (enum is_wrap do_wrap)
138 const char *result = NULL;
156 /* ========================== Styling primitives. ========================== */
159 /* When compiled in src, enable styling support.
160 When compiled in libgettextpo, don't enable styling support. */
161 #ifdef GETTEXTDATADIR
163 /* Return true if the stream is an instance of styled_ostream_t. */
165 is_stylable (ostream_t stream)
167 return IS_INSTANCE (stream, ostream, styled_ostream);
170 /* Start a run of text belonging to a given CSS class. */
172 begin_css_class (ostream_t stream, const char *classname)
174 if (is_stylable (stream))
175 styled_ostream_begin_use_class ((styled_ostream_t) stream, classname);
178 /* End a run of text belonging to a given CSS class. */
180 end_css_class (ostream_t stream, const char *classname)
182 if (is_stylable (stream))
183 styled_ostream_end_use_class ((styled_ostream_t) stream, classname);
188 #define is_stylable(stream) false
189 #define begin_css_class(stream,classname) /* empty */
190 #define end_css_class(stream,classname) /* empty */
194 /* CSS classes at message level. */
195 static const char class_header[] = "header";
196 static const char class_translated[] = "translated";
197 static const char class_untranslated[] = "untranslated";
198 static const char class_fuzzy[] = "fuzzy";
199 static const char class_obsolete[] = "obsolete";
201 /* CSS classes describing the parts of a message. */
202 static const char class_comment[] = "comment";
203 static const char class_translator_comment[] = "translator-comment";
204 static const char class_extracted_comment[] = "extracted-comment";
205 static const char class_reference_comment[] = "reference-comment";
206 static const char class_reference[] = "reference";
207 static const char class_flag_comment[] = "flag-comment";
208 static const char class_flag[] = "flag";
209 static const char class_fuzzy_flag[] = "fuzzy-flag";
210 static const char class_previous_comment[] = "previous-comment";
211 static const char class_previous[] = "previous";
212 static const char class_msgid[] = "msgid";
213 static const char class_msgstr[] = "msgstr";
214 static const char class_keyword[] = "keyword";
215 static const char class_string[] = "string";
217 /* CSS classes for the contents of strings. */
218 static const char class_text[] = "text";
219 static const char class_escape_sequence[] = "escape-sequence";
220 static const char class_format_directive[] = "format-directive";
221 static const char class_invalid_format_directive[] = "invalid-format-directive";
223 static const char class_added[] = "added";
224 static const char class_changed[] = "changed";
225 static const char class_removed[] = "removed";
228 /* Per-character attributes. */
231 ATTR_ESCAPE_SEQUENCE = 1 << 0,
232 /* The following two are exclusive. */
233 ATTR_FORMAT_DIRECTIVE = 1 << 1,
234 ATTR_INVALID_FORMAT_DIRECTIVE = 1 << 2
238 /* ================ Output parts of a message, as comments. ================ */
241 /* Output mp->comment as a set of comment lines. */
244 message_print_comment (const message_ty *mp, ostream_t stream)
246 if (mp->comment != NULL)
250 begin_css_class (stream, class_translator_comment);
252 for (j = 0; j < mp->comment->nitems; ++j)
254 const char *s = mp->comment->item[j];
258 ostream_write_str (stream, "#");
260 ostream_write_str (stream, " ");
261 e = strchr (s, '\n');
264 ostream_write_str (stream, s);
269 ostream_write_mem (stream, s, e - s);
272 ostream_write_str (stream, "\n");
277 end_css_class (stream, class_translator_comment);
282 /* Output mp->comment_dot as a set of comment lines. */
285 message_print_comment_dot (const message_ty *mp, ostream_t stream)
287 if (mp->comment_dot != NULL)
291 begin_css_class (stream, class_extracted_comment);
293 for (j = 0; j < mp->comment_dot->nitems; ++j)
295 const char *s = mp->comment_dot->item[j];
296 ostream_write_str (stream, "#.");
298 ostream_write_str (stream, " ");
299 ostream_write_str (stream, s);
300 ostream_write_str (stream, "\n");
303 end_css_class (stream, class_extracted_comment);
308 /* Output mp->filepos as a set of comment lines. */
310 static enum filepos_comment_type filepos_comment_type = filepos_comment_full;
313 message_print_comment_filepos (const message_ty *mp, ostream_t stream,
314 bool uniforum, size_t page_width)
316 if (filepos_comment_type != filepos_comment_none
317 && mp->filepos_count != 0)
319 size_t filepos_count;
322 begin_css_class (stream, class_reference_comment);
324 if (filepos_comment_type == filepos_comment_file)
329 filepos = XNMALLOC (mp->filepos_count, lex_pos_ty);
331 for (i = 0; i < mp->filepos_count; ++i)
333 lex_pos_ty *pp = &mp->filepos[i];
336 for (j = 0; j < filepos_count; j++)
337 if (strcmp (filepos[j].file_name, pp->file_name) == 0)
340 if (j == filepos_count)
342 filepos[filepos_count].file_name = pp->file_name;
343 filepos[filepos_count].line_number = (size_t)-1;
350 filepos = mp->filepos;
351 filepos_count = mp->filepos_count;
358 for (j = 0; j < filepos_count; ++j)
360 lex_pos_ty *pp = &filepos[j];
361 const char *cp = pp->file_name;
364 while (cp[0] == '.' && cp[1] == '/')
366 ostream_write_str (stream, "# ");
367 begin_css_class (stream, class_reference);
368 /* There are two Sun formats to choose from: SunOS and
369 Solaris. Use the Solaris form here. */
370 str = xasprintf ("File: %s, line: %ld",
371 cp, (long) pp->line_number);
372 ostream_write_str (stream, str);
373 end_css_class (stream, class_reference);
374 ostream_write_str (stream, "\n");
383 ostream_write_str (stream, "#:");
385 for (j = 0; j < filepos_count; ++j)
394 while (cp[0] == '.' && cp[1] == '/')
396 if (filepos_comment_type == filepos_comment_file
397 /* Some xgettext input formats, like RST, lack line
399 || pp->line_number == (size_t)(-1))
402 sprintf (buffer, ":%ld", (long) pp->line_number);
403 len = strlen (cp) + strlen (buffer) + 1;
404 if (column > 2 && column + len > page_width)
406 ostream_write_str (stream, "\n#:");
409 ostream_write_str (stream, " ");
410 begin_css_class (stream, class_reference);
411 ostream_write_str (stream, cp);
412 ostream_write_str (stream, buffer);
413 end_css_class (stream, class_reference);
416 ostream_write_str (stream, "\n");
419 if (filepos != mp->filepos)
422 end_css_class (stream, class_reference_comment);
427 /* Output mp->is_fuzzy, mp->is_format, mp->range, mp->do_wrap as a comment
431 message_print_comment_flags (const message_ty *mp, ostream_t stream, bool debug)
433 if ((mp->is_fuzzy && mp->msgstr[0] != '\0')
434 || has_significant_format_p (mp->is_format)
435 || has_range_p (mp->range)
436 || mp->do_wrap == no)
438 bool first_flag = true;
441 begin_css_class (stream, class_flag_comment);
443 ostream_write_str (stream, "#,");
445 /* We don't print the fuzzy flag if the msgstr is empty. This
446 might be introduced by the user but we want to normalize the
448 if (mp->is_fuzzy && mp->msgstr[0] != '\0')
450 ostream_write_str (stream, " ");
451 begin_css_class (stream, class_flag);
452 begin_css_class (stream, class_fuzzy_flag);
453 ostream_write_str (stream, "fuzzy");
454 end_css_class (stream, class_fuzzy_flag);
455 end_css_class (stream, class_flag);
459 for (i = 0; i < NFORMATS; i++)
460 if (significant_format_p (mp->is_format[i]))
463 ostream_write_str (stream, ",");
465 ostream_write_str (stream, " ");
466 begin_css_class (stream, class_flag);
467 ostream_write_str (stream,
468 make_format_description_string (mp->is_format[i],
471 end_css_class (stream, class_flag);
475 if (has_range_p (mp->range))
480 ostream_write_str (stream, ",");
482 ostream_write_str (stream, " ");
483 begin_css_class (stream, class_flag);
484 string = make_range_description_string (mp->range);
485 ostream_write_str (stream, string);
487 end_css_class (stream, class_flag);
491 if (mp->do_wrap == no)
494 ostream_write_str (stream, ",");
496 ostream_write_str (stream, " ");
497 begin_css_class (stream, class_flag);
498 ostream_write_str (stream,
499 make_c_width_description_string (mp->do_wrap));
500 end_css_class (stream, class_flag);
504 ostream_write_str (stream, "\n");
506 end_css_class (stream, class_flag_comment);
511 /* ========= Some parameters for use by 'msgdomain_list_print_po'. ========= */
514 /* This variable controls the extent to which the page width applies.
515 True means it applies to message strings and file reference lines.
516 False means it applies to file reference lines only. */
517 static bool wrap_strings = true;
520 message_page_width_ignore ()
522 wrap_strings = false;
526 /* These three variables control the output style of the message_print
527 function. Interface functions for them are to be used. */
528 static bool indent = false;
529 static bool uniforum = false;
530 static bool escape = false;
533 message_print_style_indent ()
539 message_print_style_uniforum ()
545 message_print_style_escape (bool flag)
551 message_print_style_filepos (enum filepos_comment_type type)
553 filepos_comment_type = type;
557 /* --add-location argument handling. Return an error indicator. */
559 handle_filepos_comment_option (const char *option)
563 if (strcmp (option, "never") == 0 || strcmp (option, "no") == 0)
564 message_print_style_filepos (filepos_comment_none);
565 else if (strcmp (option, "full") == 0 || strcmp (option, "yes") == 0)
566 message_print_style_filepos (filepos_comment_full);
567 else if (strcmp (option, "file") == 0)
568 message_print_style_filepos (filepos_comment_file);
571 fprintf (stderr, "invalid --add-location argument: %s\n", option);
576 /* --add-location is equivalent to --add-location=full. */
577 message_print_style_filepos (filepos_comment_full);
582 /* =============== msgdomain_list_print_po() and subroutines. =============== */
585 /* A version of memcpy optimized for the case n <= 1. */
587 memcpy_small (void *dst, const void *src, size_t n)
591 char *q = (char *) dst;
592 const char *p = (const char *) src;
596 do *++q = *++p; while (--n > 0);
601 /* A version of memset optimized for the case n <= 1. */
603 memset_small (void *dst, char c, size_t n)
607 char *p = (char *) dst;
611 do *++p = c; while (--n > 0);
617 wrap (const message_ty *mp, ostream_t stream,
618 const char *line_prefix, int extra_indent, const char *css_class,
619 const char *name, const char *value,
620 enum is_wrap do_wrap, size_t page_width,
623 const char *canon_charset;
634 canon_charset = po_charset_canonicalize (charset);
637 /* The old Solaris/openwin msgfmt and GNU msgfmt <= 0.10.35 don't know
638 about multibyte encodings, and require a spurious backslash after
639 every multibyte character whose last byte is 0x5C. Some programs,
640 like vim, distribute PO files in this broken format. It is important
641 for such programs that GNU msgmerge continues to support this old
642 PO file format when the Makefile requests it. */
643 envval = getenv ("OLD_PO_FILE_OUTPUT");
644 if (envval != NULL && *envval != '\0')
645 /* Write a PO file in old format, with extraneous backslashes. */
646 conv = (iconv_t)(-1);
648 if (canon_charset == NULL)
649 /* Invalid PO file encoding. */
650 conv = (iconv_t)(-1);
652 /* Avoid glibc-2.1 bug with EUC-KR. */
653 # if ((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
654 && !defined _LIBICONV_VERSION
655 if (strcmp (canon_charset, "EUC-KR") == 0)
656 conv = (iconv_t)(-1);
659 /* Avoid Solaris 2.9 bug with GB2312, EUC-TW, BIG5, BIG5-HKSCS, GBK,
661 # if defined __sun && !defined _LIBICONV_VERSION
662 if ( strcmp (canon_charset, "GB2312") == 0
663 || strcmp (canon_charset, "EUC-TW") == 0
664 || strcmp (canon_charset, "BIG5") == 0
665 || strcmp (canon_charset, "BIG5-HKSCS") == 0
666 || strcmp (canon_charset, "GBK") == 0
667 || strcmp (canon_charset, "GB18030") == 0)
668 conv = (iconv_t)(-1);
671 /* Use iconv() to parse multibyte characters. */
672 conv = iconv_open ("UTF-8", canon_charset);
674 if (conv != (iconv_t)(-1))
678 if (canon_charset == NULL)
681 weird_cjk = po_is_charset_weird_cjk (canon_charset);
683 if (canon_charset == NULL)
684 canon_charset = po_charset_ascii;
686 /* Determine the extent of format string directives. */
689 if (value[0] != '\0')
692 (strlen (name) >= 6 && memcmp (name, "msgstr", 6) == 0);
693 /* or equivalent: = (css_class == class_msgstr) */
696 for (i = 0; i < NFORMATS; i++)
697 if (possible_format_p (mp->is_format[i]))
699 size_t len = strlen (value);
700 struct formatstring_parser *parser = formatstring_parsers[i];
701 char *invalid_reason = NULL;
707 fmtdir = XCALLOC (len, char);
708 descr = parser->parse (value, is_msgstr, fmtdir, &invalid_reason);
710 parser->free (descr);
712 /* Locate the FMTDIR_* bits and transform the array to an array
714 fmtdirattr = XCALLOC (len, char);
715 fd_end = fmtdir + len;
716 for (fdp = fmtdir, fdap = fmtdirattr; fdp < fd_end; fdp++, fdap++)
717 if (*fdp & FMTDIR_START)
720 for (fdq = fdp; fdq < fd_end; fdq++)
721 if (*fdq & (FMTDIR_END | FMTDIR_ERROR))
724 /* The ->parse method has determined the start of a
725 formatstring directive but not stored a bit indicating
726 its end. It is a bug in the ->parse method. */
728 if (*fdq & FMTDIR_ERROR)
729 memset (fdap, ATTR_INVALID_FORMAT_DIRECTIVE, fdq - fdp + 1);
731 memset (fdap, ATTR_FORMAT_DIRECTIVE, fdq - fdp + 1);
742 /* Loop over the '\n' delimited portions of value. */
747 /* The usual escapes, as defined by the ANSI C Standard. */
748 # define is_escape(c) \
749 ((c) == '\a' || (c) == '\b' || (c) == '\f' || (c) == '\n' \
750 || (c) == '\r' || (c) == '\t' || (c) == '\v')
762 int startcol, startcol_after_break, width;
765 for (es = s; *es != '\0'; )
769 /* Expand escape sequences in each portion. */
770 for (ep = s, portion_len = 0; ep < es; ep++)
775 else if (escape && !c_isprint ((unsigned char) c))
777 else if (c == '\\' || c == '"')
782 if (conv != (iconv_t)(-1))
784 /* Skip over a complete multi-byte character. Don't
785 interpret the second byte of a multi-byte character as
786 ASCII. This is needed for the BIG5, BIG5-HKSCS, GBK,
787 GB18030, SHIFT_JIS, JOHAB encodings. */
789 const char *inptr = ep;
791 char *outptr = &scratchbuf[0];
792 size_t outsize = sizeof (scratchbuf);
796 for (insize = 1; inptr + insize <= es; insize++)
799 (ICONV_CONST char **) &inptr, &insize,
801 if (!(res == (size_t)(-1) && errno == EINVAL))
803 /* We expect that no input bytes have been consumed
808 if (res == (size_t)(-1))
812 po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
813 _("invalid multibyte sequence"));
816 else if (errno == EINVAL)
818 /* This could happen if an incomplete
819 multibyte sequence at the end of input
821 po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
822 _("incomplete multibyte sequence"));
829 portion_len += insize;
836 /* Special handling of encodings with CJK structure. */
838 && (unsigned char) ep[0] >= 0x80
839 && (unsigned char) ep[1] >= 0x30)
849 portion = XNMALLOC (portion_len, char);
850 overrides = XNMALLOC (portion_len, char);
851 attributes = XNMALLOC (portion_len, char);
852 for (ep = s, pp = portion, op = overrides, ap = attributes; ep < es; ep++)
855 char attr = (fmtdirattr != NULL ? fmtdirattr[ep - value] : 0);
856 char brk = UC_BREAK_UNDEFINED;
857 /* Don't break inside format directives. */
858 if (attr == ATTR_FORMAT_DIRECTIVE
859 && (fmtdir[ep - value] & FMTDIR_START) == 0)
860 brk = UC_BREAK_PROHIBITED;
865 case '\a': c = 'a'; break;
866 case '\b': c = 'b'; break;
867 case '\f': c = 'f'; break;
868 case '\n': c = 'n'; break;
869 case '\r': c = 'r'; break;
870 case '\t': c = 't'; break;
871 case '\v': c = 'v'; break;
877 *op++ = UC_BREAK_PROHIBITED;
878 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
879 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
880 /* We warn about any use of escape sequences beside
882 if (c != 'n' && c != 't')
884 char *error_message =
886 internationalized messages should not contain the '\\%c' escape sequence"),
888 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, false,
890 free (error_message);
893 else if (escape && !c_isprint ((unsigned char) c))
896 *pp++ = '0' + (((unsigned char) c >> 6) & 7);
897 *pp++ = '0' + (((unsigned char) c >> 3) & 7);
898 *pp++ = '0' + ((unsigned char) c & 7);
900 *op++ = UC_BREAK_PROHIBITED;
901 *op++ = UC_BREAK_PROHIBITED;
902 *op++ = UC_BREAK_PROHIBITED;
903 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
904 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
905 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
906 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
908 else if (c == '\\' || c == '"')
913 *op++ = UC_BREAK_PROHIBITED;
914 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
915 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
920 if (conv != (iconv_t)(-1))
922 /* Copy a complete multi-byte character. Don't
923 interpret the second byte of a multi-byte character as
924 ASCII. This is needed for the BIG5, BIG5-HKSCS, GBK,
925 GB18030, SHIFT_JIS, JOHAB encodings. */
927 const char *inptr = ep;
929 char *outptr = &scratchbuf[0];
930 size_t outsize = sizeof (scratchbuf);
934 for (insize = 1; inptr + insize <= es; insize++)
937 (ICONV_CONST char **) &inptr, &insize,
939 if (!(res == (size_t)(-1) && errno == EINVAL))
941 /* We expect that no input bytes have been consumed
946 if (res == (size_t)(-1))
950 po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0,
951 false, _("invalid multibyte sequence"));
958 memcpy_small (pp, ep, insize);
961 memset_small (op + 1, UC_BREAK_PROHIBITED, insize - 1);
963 memset_small (ap, attr, insize);
971 /* Special handling of encodings with CJK structure. */
973 && (unsigned char) c >= 0x80
974 && (unsigned char) ep[1] >= 0x30)
980 *op++ = UC_BREAK_PROHIBITED;
994 /* Don't break immediately before the "\n" at the end. */
995 if (es > s && es[-1] == '\n')
996 overrides[portion_len - 2] = UC_BREAK_PROHIBITED;
998 linebreaks = XNMALLOC (portion_len, char);
1000 /* Subsequent lines after a break are all indented.
1002 startcol_after_break = (line_prefix ? strlen (line_prefix) : 0);
1004 startcol_after_break = (startcol_after_break + extra_indent + 8) & ~7;
1005 startcol_after_break++;
1007 /* The line width. Allow room for the closing quote character. */
1008 width = (wrap_strings && do_wrap != no ? page_width : INT_MAX) - 1;
1009 /* Adjust for indentation of subsequent lines. */
1010 width -= startcol_after_break;
1013 /* The line starts with different things depending on whether it
1014 is the first line, and if we are using the indented style.
1016 startcol = (line_prefix ? strlen (line_prefix) : 0);
1019 startcol += strlen (name);
1021 startcol = (startcol + extra_indent + 8) & ~7;
1028 startcol = (startcol + extra_indent + 8) & ~7;
1030 /* Allow room for the opening quote character. */
1032 /* Adjust for indentation of subsequent lines. */
1033 startcol -= startcol_after_break;
1035 /* Do line breaking on the portion. */
1036 ulc_width_linebreaks (portion, portion_len, width, startcol, 0,
1037 overrides, canon_charset, linebreaks);
1039 /* If this is the first line, and we are not using the indented
1040 style, and the line would wrap, then use an empty first line
1042 if (first_line && !indent
1046 || memchr (linebreaks, UC_BREAK_POSSIBLE, portion_len) != NULL))
1048 if (line_prefix != NULL)
1049 ostream_write_str (stream, line_prefix);
1050 begin_css_class (stream, css_class);
1051 begin_css_class (stream, class_keyword);
1052 ostream_write_str (stream, name);
1053 end_css_class (stream, class_keyword);
1054 ostream_write_str (stream, " ");
1055 begin_css_class (stream, class_string);
1056 ostream_write_str (stream, "\"\"");
1057 end_css_class (stream, class_string);
1058 end_css_class (stream, css_class);
1059 ostream_write_str (stream, "\n");
1061 /* Recompute startcol and linebreaks. */
1065 /* Print the beginning of the line. This will depend on whether
1066 this is the first line, and if the indented style is being
1071 if (line_prefix != NULL)
1073 ostream_write_str (stream, line_prefix);
1074 currcol = strlen (line_prefix);
1076 begin_css_class (stream, css_class);
1079 begin_css_class (stream, class_keyword);
1080 ostream_write_str (stream, name);
1081 currcol += strlen (name);
1082 end_css_class (stream, class_keyword);
1085 if (extra_indent > 0)
1086 ostream_write_mem (stream, " ", extra_indent);
1087 currcol += extra_indent;
1088 ostream_write_mem (stream, " ", 8 - (currcol & 7));
1089 currcol = (currcol + 8) & ~7;
1093 ostream_write_str (stream, " ");
1102 if (extra_indent > 0)
1103 ostream_write_mem (stream, " ", extra_indent);
1104 currcol += extra_indent;
1105 ostream_write_mem (stream, " ", 8 - (currcol & 7));
1106 currcol = (currcol + 8) & ~7;
1111 /* Print the portion itself, with linebreaks where necessary. */
1115 begin_css_class (stream, class_string);
1116 ostream_write_str (stream, "\"");
1117 begin_css_class (stream, class_text);
1119 for (i = 0; i < portion_len; i++)
1121 if (linebreaks[i] == UC_BREAK_POSSIBLE)
1125 /* Change currattr so that it becomes 0. */
1126 if (currattr & ATTR_ESCAPE_SEQUENCE)
1128 end_css_class (stream, class_escape_sequence);
1129 currattr &= ~ATTR_ESCAPE_SEQUENCE;
1131 if (currattr & ATTR_FORMAT_DIRECTIVE)
1133 end_css_class (stream, class_format_directive);
1134 currattr &= ~ATTR_FORMAT_DIRECTIVE;
1136 else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1138 end_css_class (stream, class_invalid_format_directive);
1139 currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE;
1141 if (!(currattr == 0))
1144 end_css_class (stream, class_text);
1145 ostream_write_str (stream, "\"");
1146 end_css_class (stream, class_string);
1147 end_css_class (stream, css_class);
1148 ostream_write_str (stream, "\n");
1151 if (line_prefix != NULL)
1153 ostream_write_str (stream, line_prefix);
1154 currcol = strlen (line_prefix);
1156 begin_css_class (stream, css_class);
1159 ostream_write_mem (stream, " ", 8 - (currcol & 7));
1160 currcol = (currcol + 8) & ~7;
1162 begin_css_class (stream, class_string);
1163 ostream_write_str (stream, "\"");
1164 begin_css_class (stream, class_text);
1166 /* Change currattr so that it matches attributes[i]. */
1167 if (attributes[i] != currattr)
1169 /* class_escape_sequence occurs inside class_format_directive
1170 and class_invalid_format_directive, so clear it first. */
1171 if (currattr & ATTR_ESCAPE_SEQUENCE)
1173 end_css_class (stream, class_escape_sequence);
1174 currattr &= ~ATTR_ESCAPE_SEQUENCE;
1176 if (~attributes[i] & currattr & ATTR_FORMAT_DIRECTIVE)
1178 end_css_class (stream, class_format_directive);
1179 currattr &= ~ATTR_FORMAT_DIRECTIVE;
1181 else if (~attributes[i] & currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1183 end_css_class (stream, class_invalid_format_directive);
1184 currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE;
1186 if (attributes[i] & ~currattr & ATTR_FORMAT_DIRECTIVE)
1188 begin_css_class (stream, class_format_directive);
1189 currattr |= ATTR_FORMAT_DIRECTIVE;
1191 else if (attributes[i] & ~currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1193 begin_css_class (stream, class_invalid_format_directive);
1194 currattr |= ATTR_INVALID_FORMAT_DIRECTIVE;
1196 /* class_escape_sequence occurs inside class_format_directive
1197 and class_invalid_format_directive, so set it last. */
1198 if (attributes[i] & ~currattr & ATTR_ESCAPE_SEQUENCE)
1200 begin_css_class (stream, class_escape_sequence);
1201 currattr |= ATTR_ESCAPE_SEQUENCE;
1204 ostream_write_mem (stream, &portion[i], 1);
1207 /* Change currattr so that it becomes 0. */
1208 if (currattr & ATTR_ESCAPE_SEQUENCE)
1210 end_css_class (stream, class_escape_sequence);
1211 currattr &= ~ATTR_ESCAPE_SEQUENCE;
1213 if (currattr & ATTR_FORMAT_DIRECTIVE)
1215 end_css_class (stream, class_format_directive);
1216 currattr &= ~ATTR_FORMAT_DIRECTIVE;
1218 else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1220 end_css_class (stream, class_invalid_format_directive);
1221 currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE;
1223 if (!(currattr == 0))
1226 end_css_class (stream, class_text);
1227 ostream_write_str (stream, "\"");
1228 end_css_class (stream, class_string);
1229 end_css_class (stream, css_class);
1230 ostream_write_str (stream, "\n");
1243 if (fmtdirattr != NULL)
1249 if (conv != (iconv_t)(-1))
1256 print_blank_line (ostream_t stream)
1260 begin_css_class (stream, class_comment);
1261 ostream_write_str (stream, "#\n");
1262 end_css_class (stream, class_comment);
1265 ostream_write_str (stream, "\n");
1270 message_print (const message_ty *mp, ostream_t stream,
1271 const char *charset, size_t page_width, bool blank_line,
1276 /* Separate messages with a blank line. Uniforum doesn't like blank
1277 lines, so use an empty comment (unless there already is one). */
1278 if (blank_line && (!uniforum
1279 || mp->comment == NULL
1280 || mp->comment->nitems == 0
1281 || mp->comment->item[0][0] != '\0'))
1282 print_blank_line (stream);
1285 begin_css_class (stream, class_header);
1286 else if (mp->msgstr[0] == '\0')
1287 begin_css_class (stream, class_untranslated);
1288 else if (mp->is_fuzzy)
1289 begin_css_class (stream, class_fuzzy);
1291 begin_css_class (stream, class_translated);
1293 begin_css_class (stream, class_comment);
1295 /* Print translator comment if available. */
1296 message_print_comment (mp, stream);
1298 /* Print xgettext extracted comments. */
1299 message_print_comment_dot (mp, stream);
1301 /* Print the file position comments. This will help a human who is
1302 trying to navigate the sources. There is no problem of getting
1303 repeated positions, because duplicates are checked for. */
1304 message_print_comment_filepos (mp, stream, uniforum, page_width);
1306 /* Print flag information in special comment. */
1307 message_print_comment_flags (mp, stream, debug);
1309 /* Print the previous msgid. This helps the translator when the msgid has
1310 only slightly changed. */
1311 begin_css_class (stream, class_previous_comment);
1312 if (mp->prev_msgctxt != NULL)
1313 wrap (mp, stream, "#| ", 0, class_previous, "msgctxt", mp->prev_msgctxt,
1314 mp->do_wrap, page_width, charset);
1315 if (mp->prev_msgid != NULL)
1316 wrap (mp, stream, "#| ", 0, class_previous, "msgid", mp->prev_msgid,
1317 mp->do_wrap, page_width, charset);
1318 if (mp->prev_msgid_plural != NULL)
1319 wrap (mp, stream, "#| ", 0, class_previous, "msgid_plural",
1320 mp->prev_msgid_plural, mp->do_wrap, page_width, charset);
1321 end_css_class (stream, class_previous_comment);
1322 extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL
1323 || mp->prev_msgid_plural != NULL
1327 end_css_class (stream, class_comment);
1329 /* Print each of the message components. Wrap them nicely so they
1330 are as readable as possible. If there is no recorded msgstr for
1331 this domain, emit an empty string. */
1332 if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt)
1333 && po_charset_canonicalize (charset) != po_charset_utf8)
1335 char *warning_message =
1337 The following msgctxt contains non-ASCII characters.\n\
1338 This will cause problems to translators who use a character encoding\n\
1339 different from yours. Consider using a pure ASCII msgctxt instead.\n\
1340 %s\n"), mp->msgctxt);
1341 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1342 free (warning_message);
1344 if (!is_ascii_string (mp->msgid)
1345 && po_charset_canonicalize (charset) != po_charset_utf8)
1347 char *warning_message =
1349 The following msgid contains non-ASCII characters.\n\
1350 This will cause problems to translators who use a character encoding\n\
1351 different from yours. Consider using a pure ASCII msgid instead.\n\
1353 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1354 free (warning_message);
1356 if (mp->msgctxt != NULL)
1357 wrap (mp, stream, NULL, extra_indent, class_msgid, "msgctxt", mp->msgctxt,
1358 mp->do_wrap, page_width, charset);
1359 wrap (mp, stream, NULL, extra_indent, class_msgid, "msgid", mp->msgid,
1360 mp->do_wrap, page_width, charset);
1361 if (mp->msgid_plural != NULL)
1362 wrap (mp, stream, NULL, extra_indent, class_msgid, "msgid_plural",
1363 mp->msgid_plural, mp->do_wrap, page_width, charset);
1365 if (mp->msgid_plural == NULL)
1366 wrap (mp, stream, NULL, extra_indent, class_msgstr, "msgstr", mp->msgstr,
1367 mp->do_wrap, page_width, charset);
1370 char prefix_buf[20];
1374 for (p = mp->msgstr, i = 0;
1375 p < mp->msgstr + mp->msgstr_len;
1376 p += strlen (p) + 1, i++)
1378 sprintf (prefix_buf, "msgstr[%u]", i);
1379 wrap (mp, stream, NULL, extra_indent, class_msgstr, prefix_buf, p,
1380 mp->do_wrap, page_width, charset);
1385 end_css_class (stream, class_header);
1386 else if (mp->msgstr[0] == '\0')
1387 end_css_class (stream, class_untranslated);
1388 else if (mp->is_fuzzy)
1389 end_css_class (stream, class_fuzzy);
1391 end_css_class (stream, class_translated);
1396 message_print_obsolete (const message_ty *mp, ostream_t stream,
1397 const char *charset, size_t page_width, bool blank_line)
1401 /* If msgstr is the empty string we print nothing. */
1402 if (mp->msgstr[0] == '\0')
1405 /* Separate messages with a blank line. Uniforum doesn't like blank
1406 lines, so use an empty comment (unless there already is one). */
1408 print_blank_line (stream);
1410 begin_css_class (stream, class_obsolete);
1412 begin_css_class (stream, class_comment);
1414 /* Print translator comment if available. */
1415 message_print_comment (mp, stream);
1417 /* Print xgettext extracted comments (normally empty). */
1418 message_print_comment_dot (mp, stream);
1420 /* Print the file position comments (normally empty). */
1421 message_print_comment_filepos (mp, stream, uniforum, page_width);
1423 /* Print flag information in special comment. */
1426 ostream_write_str (stream, "#,");
1429 ostream_write_str (stream, " fuzzy");
1431 ostream_write_str (stream, "\n");
1434 /* Print the previous msgid. This helps the translator when the msgid has
1435 only slightly changed. */
1436 begin_css_class (stream, class_previous_comment);
1437 if (mp->prev_msgctxt != NULL)
1438 wrap (mp, stream, "#~| ", 0, class_previous, "msgctxt", mp->prev_msgctxt,
1439 mp->do_wrap, page_width, charset);
1440 if (mp->prev_msgid != NULL)
1441 wrap (mp, stream, "#~| ", 0, class_previous, "msgid", mp->prev_msgid,
1442 mp->do_wrap, page_width, charset);
1443 if (mp->prev_msgid_plural != NULL)
1444 wrap (mp, stream, "#~| ", 0, class_previous, "msgid_plural",
1445 mp->prev_msgid_plural, mp->do_wrap, page_width, charset);
1446 end_css_class (stream, class_previous_comment);
1447 extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL
1448 || mp->prev_msgid_plural != NULL
1452 end_css_class (stream, class_comment);
1454 /* Print each of the message components. Wrap them nicely so they
1455 are as readable as possible. */
1456 if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt)
1457 && po_charset_canonicalize (charset) != po_charset_utf8)
1459 char *warning_message =
1461 The following msgctxt contains non-ASCII characters.\n\
1462 This will cause problems to translators who use a character encoding\n\
1463 different from yours. Consider using a pure ASCII msgctxt instead.\n\
1464 %s\n"), mp->msgctxt);
1465 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1466 free (warning_message);
1468 if (!is_ascii_string (mp->msgid)
1469 && po_charset_canonicalize (charset) != po_charset_utf8)
1471 char *warning_message =
1473 The following msgid contains non-ASCII characters.\n\
1474 This will cause problems to translators who use a character encoding\n\
1475 different from yours. Consider using a pure ASCII msgid instead.\n\
1477 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1478 free (warning_message);
1480 if (mp->msgctxt != NULL)
1481 wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgctxt", mp->msgctxt,
1482 mp->do_wrap, page_width, charset);
1483 wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgid", mp->msgid,
1484 mp->do_wrap, page_width, charset);
1485 if (mp->msgid_plural != NULL)
1486 wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgid_plural",
1487 mp->msgid_plural, mp->do_wrap, page_width, charset);
1489 if (mp->msgid_plural == NULL)
1490 wrap (mp, stream, "#~ ", extra_indent, class_msgstr, "msgstr", mp->msgstr,
1491 mp->do_wrap, page_width, charset);
1494 char prefix_buf[20];
1498 for (p = mp->msgstr, i = 0;
1499 p < mp->msgstr + mp->msgstr_len;
1500 p += strlen (p) + 1, i++)
1502 sprintf (prefix_buf, "msgstr[%u]", i);
1503 wrap (mp, stream, "#~ ", extra_indent, class_msgstr, prefix_buf, p,
1504 mp->do_wrap, page_width, charset);
1508 end_css_class (stream, class_obsolete);
1513 msgdomain_list_print_po (msgdomain_list_ty *mdlp, ostream_t stream,
1514 size_t page_width, bool debug)
1519 /* Write out the messages for each domain. */
1521 for (k = 0; k < mdlp->nitems; k++)
1523 message_list_ty *mlp;
1525 const char *charset;
1526 char *allocated_charset;
1528 /* If the first domain is the default, don't bother emitting
1529 the domain name, because it is the default. */
1531 && strcmp (mdlp->item[k]->domain, MESSAGE_DOMAIN_DEFAULT) == 0))
1534 print_blank_line (stream);
1535 begin_css_class (stream, class_keyword);
1536 ostream_write_str (stream, "domain");
1537 end_css_class (stream, class_keyword);
1538 ostream_write_str (stream, " ");
1539 begin_css_class (stream, class_string);
1540 ostream_write_str (stream, "\"");
1541 begin_css_class (stream, class_text);
1542 ostream_write_str (stream, mdlp->item[k]->domain);
1543 end_css_class (stream, class_text);
1544 ostream_write_str (stream, "\"");
1545 end_css_class (stream, class_string);
1546 ostream_write_str (stream, "\n");
1550 mlp = mdlp->item[k]->messages;
1552 /* Search the header entry. */
1554 for (j = 0; j < mlp->nitems; ++j)
1555 if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1557 header = mlp->item[j]->msgstr;
1561 /* Extract the charset name. */
1563 allocated_charset = NULL;
1566 const char *charsetstr = c_strstr (header, "charset=");
1568 if (charsetstr != NULL)
1572 charsetstr += strlen ("charset=");
1573 len = strcspn (charsetstr, " \t\n");
1574 allocated_charset = (char *) xmalloca (len + 1);
1575 memcpy (allocated_charset, charsetstr, len);
1576 allocated_charset[len] = '\0';
1577 charset = allocated_charset;
1579 /* Treat the dummy default value as if it were absent. */
1580 if (strcmp (charset, "CHARSET") == 0)
1585 /* Write out each of the messages for this domain. */
1586 for (j = 0; j < mlp->nitems; ++j)
1587 if (!mlp->item[j]->obsolete)
1589 message_print (mlp->item[j], stream, charset, page_width,
1594 /* Write out each of the obsolete messages for this domain. */
1595 for (j = 0; j < mlp->nitems; ++j)
1596 if (mlp->item[j]->obsolete)
1598 message_print_obsolete (mlp->item[j], stream, charset, page_width,
1603 if (allocated_charset != NULL)
1604 freea (allocated_charset);
1609 /* Describes a PO file in .po syntax. */
1610 const struct catalog_output_format output_format_po =
1612 msgdomain_list_print_po, /* print */
1613 false, /* requires_utf8 */
1614 true, /* supports_color */
1615 true, /* supports_multiple_domains */
1616 true, /* supports_contexts */
1617 true, /* supports_plurals */
1618 true, /* sorts_obsoletes_to_end */
1619 false, /* alternative_is_po */
1620 false /* alternative_is_java_class */