1 /* GNU gettext - internationalization aids
2 Copyright (C) 1995-1998, 2000-2010, 2012 Free Software Foundation, Inc.
4 This file was written by Peter Miller <millerp@canb.auug.org.au>
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.
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.
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/>. */
38 #include "po-charset.h"
41 #include "msgl-ascii.h"
42 #include "write-catalog.h"
48 # include "styled-ostream.h"
50 #include "xvasprintf.h"
51 #include "po-xerror.h"
54 /* Our regular abbreviation. */
55 #define _(str) gettext (str)
57 #if HAVE_DECL_PUTC_UNLOCKED
59 # define putc putc_unlocked
63 /* =================== Putting together a #, flags line. =================== */
66 /* Convert IS_FORMAT in the context of programming language LANG to a flag
67 string for use in #, flags. */
70 make_format_description_string (enum is_format is_format, const char *lang,
73 static char result[100];
80 sprintf (result, "possible-%s-format", lang);
84 case yes_according_to_context:
86 sprintf (result, "%s-format", lang);
89 sprintf (result, "no-%s-format", lang);
92 /* The others have already been filtered out by significant_format_p. */
100 /* Return true if IS_FORMAT is worth mentioning in a #, flags list. */
103 significant_format_p (enum is_format is_format)
105 return is_format != undecided && is_format != impossible;
109 /* Return true if one of IS_FORMAT is worth mentioning in a #, flags list. */
112 has_significant_format_p (const enum is_format is_format[NFORMATS])
116 for (i = 0; i < NFORMATS; i++)
117 if (significant_format_p (is_format[i]))
123 /* Convert a RANGE to a freshly allocated string for use in #, flags. */
126 make_range_description_string (struct argument_range range)
128 return xasprintf ("range: %d..%d", range.min, range.max);
132 /* Convert a wrapping flag DO_WRAP to a string for use in #, flags. */
135 make_c_width_description_string (enum is_wrap do_wrap)
137 const char *result = NULL;
155 /* ========================== Styling primitives. ========================== */
158 /* When compiled in src, enable styling support.
159 When compiled in libgettextpo, don't enable styling support. */
160 #ifdef GETTEXTDATADIR
162 /* Return true if the stream is an instance of styled_ostream_t. */
164 is_stylable (ostream_t stream)
166 return IS_INSTANCE (stream, ostream, styled_ostream);
169 /* Start a run of text belonging to a given CSS class. */
171 begin_css_class (ostream_t stream, const char *classname)
173 if (is_stylable (stream))
174 styled_ostream_begin_use_class ((styled_ostream_t) stream, classname);
177 /* End a run of text belonging to a given CSS class. */
179 end_css_class (ostream_t stream, const char *classname)
181 if (is_stylable (stream))
182 styled_ostream_end_use_class ((styled_ostream_t) stream, classname);
187 #define is_stylable(stream) false
188 #define begin_css_class(stream,classname) /* empty */
189 #define end_css_class(stream,classname) /* empty */
193 /* CSS classes at message level. */
194 static const char class_header[] = "header";
195 static const char class_translated[] = "translated";
196 static const char class_untranslated[] = "untranslated";
197 static const char class_fuzzy[] = "fuzzy";
198 static const char class_obsolete[] = "obsolete";
200 /* CSS classes describing the parts of a message. */
201 static const char class_comment[] = "comment";
202 static const char class_translator_comment[] = "translator-comment";
203 static const char class_extracted_comment[] = "extracted-comment";
204 static const char class_reference_comment[] = "reference-comment";
205 static const char class_reference[] = "reference";
206 static const char class_flag_comment[] = "flag-comment";
207 static const char class_flag[] = "flag";
208 static const char class_fuzzy_flag[] = "fuzzy-flag";
209 static const char class_previous_comment[] = "previous-comment";
210 static const char class_previous[] = "previous";
211 static const char class_msgid[] = "msgid";
212 static const char class_msgstr[] = "msgstr";
213 static const char class_keyword[] = "keyword";
214 static const char class_string[] = "string";
216 /* CSS classes for the contents of strings. */
217 static const char class_text[] = "text";
218 static const char class_escape_sequence[] = "escape-sequence";
219 static const char class_format_directive[] = "format-directive";
220 static const char class_invalid_format_directive[] = "invalid-format-directive";
222 static const char class_added[] = "added";
223 static const char class_changed[] = "changed";
224 static const char class_removed[] = "removed";
227 /* Per-character attributes. */
230 ATTR_ESCAPE_SEQUENCE = 1 << 0,
231 /* The following two are exclusive. */
232 ATTR_FORMAT_DIRECTIVE = 1 << 1,
233 ATTR_INVALID_FORMAT_DIRECTIVE = 1 << 2
237 /* ================ Output parts of a message, as comments. ================ */
240 /* Output mp->comment as a set of comment lines. */
243 message_print_comment (const message_ty *mp, ostream_t stream)
245 if (mp->comment != NULL)
249 begin_css_class (stream, class_translator_comment);
251 for (j = 0; j < mp->comment->nitems; ++j)
253 const char *s = mp->comment->item[j];
257 ostream_write_str (stream, "#");
259 ostream_write_str (stream, " ");
260 e = strchr (s, '\n');
263 ostream_write_str (stream, s);
268 ostream_write_mem (stream, s, e - s);
271 ostream_write_str (stream, "\n");
276 end_css_class (stream, class_translator_comment);
281 /* Output mp->comment_dot as a set of comment lines. */
284 message_print_comment_dot (const message_ty *mp, ostream_t stream)
286 if (mp->comment_dot != NULL)
290 begin_css_class (stream, class_extracted_comment);
292 for (j = 0; j < mp->comment_dot->nitems; ++j)
294 const char *s = mp->comment_dot->item[j];
295 ostream_write_str (stream, "#.");
297 ostream_write_str (stream, " ");
298 ostream_write_str (stream, s);
299 ostream_write_str (stream, "\n");
302 end_css_class (stream, class_extracted_comment);
307 /* Output mp->filepos as a set of comment lines. */
310 message_print_comment_filepos (const message_ty *mp, ostream_t stream,
311 bool uniforum, size_t page_width)
313 if (mp->filepos_count != 0)
315 begin_css_class (stream, class_reference_comment);
321 for (j = 0; j < mp->filepos_count; ++j)
323 lex_pos_ty *pp = &mp->filepos[j];
324 const char *cp = pp->file_name;
327 while (cp[0] == '.' && cp[1] == '/')
329 ostream_write_str (stream, "# ");
330 begin_css_class (stream, class_reference);
331 /* There are two Sun formats to choose from: SunOS and
332 Solaris. Use the Solaris form here. */
333 str = xasprintf ("File: %s, line: %ld",
334 cp, (long) pp->line_number);
335 ostream_write_str (stream, str);
336 end_css_class (stream, class_reference);
337 ostream_write_str (stream, "\n");
346 ostream_write_str (stream, "#:");
348 for (j = 0; j < mp->filepos_count; ++j)
355 pp = &mp->filepos[j];
357 while (cp[0] == '.' && cp[1] == '/')
359 /* Some xgettext input formats, like RST, lack line numbers. */
360 if (pp->line_number == (size_t)(-1))
363 sprintf (buffer, ":%ld", (long) pp->line_number);
364 len = strlen (cp) + strlen (buffer) + 1;
365 if (column > 2 && column + len >= page_width)
367 ostream_write_str (stream, "\n#:");
370 ostream_write_str (stream, " ");
371 begin_css_class (stream, class_reference);
372 ostream_write_str (stream, cp);
373 ostream_write_str (stream, buffer);
374 end_css_class (stream, class_reference);
377 ostream_write_str (stream, "\n");
380 end_css_class (stream, class_reference_comment);
385 /* Output mp->is_fuzzy, mp->is_format, mp->range, mp->do_wrap as a comment
389 message_print_comment_flags (const message_ty *mp, ostream_t stream, bool debug)
391 if ((mp->is_fuzzy && mp->msgstr[0] != '\0')
392 || has_significant_format_p (mp->is_format)
393 || has_range_p (mp->range)
394 || mp->do_wrap == no)
396 bool first_flag = true;
399 begin_css_class (stream, class_flag_comment);
401 ostream_write_str (stream, "#,");
403 /* We don't print the fuzzy flag if the msgstr is empty. This
404 might be introduced by the user but we want to normalize the
406 if (mp->is_fuzzy && mp->msgstr[0] != '\0')
408 ostream_write_str (stream, " ");
409 begin_css_class (stream, class_flag);
410 begin_css_class (stream, class_fuzzy_flag);
411 ostream_write_str (stream, "fuzzy");
412 end_css_class (stream, class_fuzzy_flag);
413 end_css_class (stream, class_flag);
417 for (i = 0; i < NFORMATS; i++)
418 if (significant_format_p (mp->is_format[i]))
421 ostream_write_str (stream, ",");
423 ostream_write_str (stream, " ");
424 begin_css_class (stream, class_flag);
425 ostream_write_str (stream,
426 make_format_description_string (mp->is_format[i],
429 end_css_class (stream, class_flag);
433 if (has_range_p (mp->range))
438 ostream_write_str (stream, ",");
440 ostream_write_str (stream, " ");
441 begin_css_class (stream, class_flag);
442 string = make_range_description_string (mp->range);
443 ostream_write_str (stream, string);
445 end_css_class (stream, class_flag);
449 if (mp->do_wrap == no)
452 ostream_write_str (stream, ",");
454 ostream_write_str (stream, " ");
455 begin_css_class (stream, class_flag);
456 ostream_write_str (stream,
457 make_c_width_description_string (mp->do_wrap));
458 end_css_class (stream, class_flag);
462 ostream_write_str (stream, "\n");
464 end_css_class (stream, class_flag_comment);
469 /* ========= Some parameters for use by 'msgdomain_list_print_po'. ========= */
472 /* This variable controls the extent to which the page width applies.
473 True means it applies to message strings and file reference lines.
474 False means it applies to file reference lines only. */
475 static bool wrap_strings = true;
478 message_page_width_ignore ()
480 wrap_strings = false;
484 /* These three variables control the output style of the message_print
485 function. Interface functions for them are to be used. */
486 static bool indent = false;
487 static bool uniforum = false;
488 static bool escape = false;
491 message_print_style_indent ()
497 message_print_style_uniforum ()
503 message_print_style_escape (bool flag)
509 /* =============== msgdomain_list_print_po() and subroutines. =============== */
512 /* A version of memcpy optimized for the case n <= 1. */
514 memcpy_small (void *dst, const void *src, size_t n)
518 char *q = (char *) dst;
519 const char *p = (const char *) src;
523 do *++q = *++p; while (--n > 0);
528 /* A version of memset optimized for the case n <= 1. */
530 memset_small (void *dst, char c, size_t n)
534 char *p = (char *) dst;
538 do *++p = c; while (--n > 0);
544 wrap (const message_ty *mp, ostream_t stream,
545 const char *line_prefix, int extra_indent, const char *css_class,
546 const char *name, const char *value,
547 enum is_wrap do_wrap, size_t page_width,
550 const char *canon_charset;
561 canon_charset = po_charset_canonicalize (charset);
564 /* The old Solaris/openwin msgfmt and GNU msgfmt <= 0.10.35 don't know
565 about multibyte encodings, and require a spurious backslash after
566 every multibyte character whose last byte is 0x5C. Some programs,
567 like vim, distribute PO files in this broken format. It is important
568 for such programs that GNU msgmerge continues to support this old
569 PO file format when the Makefile requests it. */
570 envval = getenv ("OLD_PO_FILE_OUTPUT");
571 if (envval != NULL && *envval != '\0')
572 /* Write a PO file in old format, with extraneous backslashes. */
573 conv = (iconv_t)(-1);
575 if (canon_charset == NULL)
576 /* Invalid PO file encoding. */
577 conv = (iconv_t)(-1);
579 /* Avoid glibc-2.1 bug with EUC-KR. */
580 # if ((__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1) && !defined __UCLIBC__) \
581 && !defined _LIBICONV_VERSION
582 if (strcmp (canon_charset, "EUC-KR") == 0)
583 conv = (iconv_t)(-1);
586 /* Avoid Solaris 2.9 bug with GB2312, EUC-TW, BIG5, BIG5-HKSCS, GBK,
588 # if defined __sun && !defined _LIBICONV_VERSION
589 if ( strcmp (canon_charset, "GB2312") == 0
590 || strcmp (canon_charset, "EUC-TW") == 0
591 || strcmp (canon_charset, "BIG5") == 0
592 || strcmp (canon_charset, "BIG5-HKSCS") == 0
593 || strcmp (canon_charset, "GBK") == 0
594 || strcmp (canon_charset, "GB18030") == 0)
595 conv = (iconv_t)(-1);
598 /* Use iconv() to parse multibyte characters. */
599 conv = iconv_open ("UTF-8", canon_charset);
601 if (conv != (iconv_t)(-1))
605 if (canon_charset == NULL)
608 weird_cjk = po_is_charset_weird_cjk (canon_charset);
610 if (canon_charset == NULL)
611 canon_charset = po_charset_ascii;
613 /* Determine the extent of format string directives. */
616 if (value[0] != '\0')
619 (strlen (name) >= 6 && memcmp (name, "msgstr", 6) == 0);
620 /* or equivalent: = (css_class == class_msgstr) */
623 for (i = 0; i < NFORMATS; i++)
624 if (possible_format_p (mp->is_format[i]))
626 size_t len = strlen (value);
627 struct formatstring_parser *parser = formatstring_parsers[i];
628 char *invalid_reason = NULL;
634 fmtdir = XCALLOC (len, char);
635 descr = parser->parse (value, is_msgstr, fmtdir, &invalid_reason);
637 parser->free (descr);
639 /* Locate the FMTDIR_* bits and transform the array to an array
641 fmtdirattr = XCALLOC (len, char);
642 fd_end = fmtdir + len;
643 for (fdp = fmtdir, fdap = fmtdirattr; fdp < fd_end; fdp++, fdap++)
644 if (*fdp & FMTDIR_START)
647 for (fdq = fdp; fdq < fd_end; fdq++)
648 if (*fdq & (FMTDIR_END | FMTDIR_ERROR))
651 /* The ->parse method has determined the start of a
652 formatstring directive but not stored a bit indicating
653 its end. It is a bug in the ->parse method. */
655 if (*fdq & FMTDIR_ERROR)
656 memset (fdap, ATTR_INVALID_FORMAT_DIRECTIVE, fdq - fdp + 1);
658 memset (fdap, ATTR_FORMAT_DIRECTIVE, fdq - fdp + 1);
669 /* Loop over the '\n' delimited portions of value. */
674 /* The usual escapes, as defined by the ANSI C Standard. */
675 # define is_escape(c) \
676 ((c) == '\a' || (c) == '\b' || (c) == '\f' || (c) == '\n' \
677 || (c) == '\r' || (c) == '\t' || (c) == '\v')
689 int startcol, startcol_after_break, width;
692 for (es = s; *es != '\0'; )
696 /* Expand escape sequences in each portion. */
697 for (ep = s, portion_len = 0; ep < es; ep++)
702 else if (escape && !c_isprint ((unsigned char) c))
704 else if (c == '\\' || c == '"')
709 if (conv != (iconv_t)(-1))
711 /* Skip over a complete multi-byte character. Don't
712 interpret the second byte of a multi-byte character as
713 ASCII. This is needed for the BIG5, BIG5-HKSCS, GBK,
714 GB18030, SHIFT_JIS, JOHAB encodings. */
716 const char *inptr = ep;
718 char *outptr = &scratchbuf[0];
719 size_t outsize = sizeof (scratchbuf);
723 for (insize = 1; inptr + insize <= es; insize++)
726 (ICONV_CONST char **) &inptr, &insize,
728 if (!(res == (size_t)(-1) && errno == EINVAL))
730 /* We expect that no input bytes have been consumed
735 if (res == (size_t)(-1))
739 po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
740 _("invalid multibyte sequence"));
747 portion_len += insize;
754 /* Special handling of encodings with CJK structure. */
756 && (unsigned char) ep[0] >= 0x80
757 && (unsigned char) ep[1] >= 0x30)
767 portion = XNMALLOC (portion_len, char);
768 overrides = XNMALLOC (portion_len, char);
769 attributes = XNMALLOC (portion_len, char);
770 for (ep = s, pp = portion, op = overrides, ap = attributes; ep < es; ep++)
773 char attr = (fmtdirattr != NULL ? fmtdirattr[ep - value] : 0);
774 char brk = UC_BREAK_UNDEFINED;
775 /* Don't break inside format directives. */
776 if (attr == ATTR_FORMAT_DIRECTIVE
777 && (fmtdir[ep - value] & FMTDIR_START) == 0)
778 brk = UC_BREAK_PROHIBITED;
783 case '\a': c = 'a'; break;
784 case '\b': c = 'b'; break;
785 case '\f': c = 'f'; break;
786 case '\n': c = 'n'; break;
787 case '\r': c = 'r'; break;
788 case '\t': c = 't'; break;
789 case '\v': c = 'v'; break;
795 *op++ = UC_BREAK_PROHIBITED;
796 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
797 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
798 /* We warn about any use of escape sequences beside
800 if (c != 'n' && c != 't')
802 char *error_message =
804 internationalized messages should not contain the '\\%c' escape sequence"),
806 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, false,
808 free (error_message);
811 else if (escape && !c_isprint ((unsigned char) c))
814 *pp++ = '0' + (((unsigned char) c >> 6) & 7);
815 *pp++ = '0' + (((unsigned char) c >> 3) & 7);
816 *pp++ = '0' + ((unsigned char) c & 7);
818 *op++ = UC_BREAK_PROHIBITED;
819 *op++ = UC_BREAK_PROHIBITED;
820 *op++ = UC_BREAK_PROHIBITED;
821 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
822 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
823 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
824 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
826 else if (c == '\\' || c == '"')
831 *op++ = UC_BREAK_PROHIBITED;
832 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
833 *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
838 if (conv != (iconv_t)(-1))
840 /* Copy a complete multi-byte character. Don't
841 interpret the second byte of a multi-byte character as
842 ASCII. This is needed for the BIG5, BIG5-HKSCS, GBK,
843 GB18030, SHIFT_JIS, JOHAB encodings. */
845 const char *inptr = ep;
847 char *outptr = &scratchbuf[0];
848 size_t outsize = sizeof (scratchbuf);
852 for (insize = 1; inptr + insize <= es; insize++)
855 (ICONV_CONST char **) &inptr, &insize,
857 if (!(res == (size_t)(-1) && errno == EINVAL))
859 /* We expect that no input bytes have been consumed
864 if (res == (size_t)(-1))
868 po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0,
869 false, _("invalid multibyte sequence"));
876 memcpy_small (pp, ep, insize);
879 memset_small (op + 1, UC_BREAK_PROHIBITED, insize - 1);
881 memset_small (ap, attr, insize);
889 /* Special handling of encodings with CJK structure. */
891 && (unsigned char) c >= 0x80
892 && (unsigned char) ep[1] >= 0x30)
898 *op++ = UC_BREAK_PROHIBITED;
912 /* Don't break immediately before the "\n" at the end. */
913 if (es > s && es[-1] == '\n')
914 overrides[portion_len - 2] = UC_BREAK_PROHIBITED;
916 linebreaks = XNMALLOC (portion_len, char);
918 /* Subsequent lines after a break are all indented.
920 startcol_after_break = (line_prefix ? strlen (line_prefix) : 0);
922 startcol_after_break = (startcol_after_break + extra_indent + 8) & ~7;
923 startcol_after_break++;
925 /* The line width. Allow room for the closing quote character. */
926 width = (wrap_strings && do_wrap != no ? page_width : INT_MAX) - 1;
927 /* Adjust for indentation of subsequent lines. */
928 width -= startcol_after_break;
931 /* The line starts with different things depending on whether it
932 is the first line, and if we are using the indented style.
934 startcol = (line_prefix ? strlen (line_prefix) : 0);
937 startcol += strlen (name);
939 startcol = (startcol + extra_indent + 8) & ~7;
946 startcol = (startcol + extra_indent + 8) & ~7;
948 /* Allow room for the opening quote character. */
950 /* Adjust for indentation of subsequent lines. */
951 startcol -= startcol_after_break;
953 /* Do line breaking on the portion. */
954 ulc_width_linebreaks (portion, portion_len, width, startcol, 0,
955 overrides, canon_charset, linebreaks);
957 /* If this is the first line, and we are not using the indented
958 style, and the line would wrap, then use an empty first line
960 if (first_line && !indent
964 || memchr (linebreaks, UC_BREAK_POSSIBLE, portion_len) != NULL))
966 if (line_prefix != NULL)
967 ostream_write_str (stream, line_prefix);
968 begin_css_class (stream, css_class);
969 begin_css_class (stream, class_keyword);
970 ostream_write_str (stream, name);
971 end_css_class (stream, class_keyword);
972 ostream_write_str (stream, " ");
973 begin_css_class (stream, class_string);
974 ostream_write_str (stream, "\"\"");
975 end_css_class (stream, class_string);
976 end_css_class (stream, css_class);
977 ostream_write_str (stream, "\n");
979 /* Recompute startcol and linebreaks. */
983 /* Print the beginning of the line. This will depend on whether
984 this is the first line, and if the indented style is being
989 if (line_prefix != NULL)
991 ostream_write_str (stream, line_prefix);
992 currcol = strlen (line_prefix);
994 begin_css_class (stream, css_class);
997 begin_css_class (stream, class_keyword);
998 ostream_write_str (stream, name);
999 currcol += strlen (name);
1000 end_css_class (stream, class_keyword);
1003 if (extra_indent > 0)
1004 ostream_write_mem (stream, " ", extra_indent);
1005 currcol += extra_indent;
1006 ostream_write_mem (stream, " ", 8 - (currcol & 7));
1007 currcol = (currcol + 8) & ~7;
1011 ostream_write_str (stream, " ");
1020 if (extra_indent > 0)
1021 ostream_write_mem (stream, " ", extra_indent);
1022 currcol += extra_indent;
1023 ostream_write_mem (stream, " ", 8 - (currcol & 7));
1024 currcol = (currcol + 8) & ~7;
1029 /* Print the portion itself, with linebreaks where necessary. */
1033 begin_css_class (stream, class_string);
1034 ostream_write_str (stream, "\"");
1035 begin_css_class (stream, class_text);
1037 for (i = 0; i < portion_len; i++)
1039 if (linebreaks[i] == UC_BREAK_POSSIBLE)
1043 /* Change currattr so that it becomes 0. */
1044 if (currattr & ATTR_ESCAPE_SEQUENCE)
1046 end_css_class (stream, class_escape_sequence);
1047 currattr &= ~ATTR_ESCAPE_SEQUENCE;
1049 if (currattr & ATTR_FORMAT_DIRECTIVE)
1051 end_css_class (stream, class_format_directive);
1052 currattr &= ~ATTR_FORMAT_DIRECTIVE;
1054 else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1056 end_css_class (stream, class_invalid_format_directive);
1057 currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE;
1059 if (!(currattr == 0))
1062 end_css_class (stream, class_text);
1063 ostream_write_str (stream, "\"");
1064 end_css_class (stream, class_string);
1065 end_css_class (stream, css_class);
1066 ostream_write_str (stream, "\n");
1069 if (line_prefix != NULL)
1071 ostream_write_str (stream, line_prefix);
1072 currcol = strlen (line_prefix);
1074 begin_css_class (stream, css_class);
1077 ostream_write_mem (stream, " ", 8 - (currcol & 7));
1078 currcol = (currcol + 8) & ~7;
1080 begin_css_class (stream, class_string);
1081 ostream_write_str (stream, "\"");
1082 begin_css_class (stream, class_text);
1084 /* Change currattr so that it matches attributes[i]. */
1085 if (attributes[i] != currattr)
1087 /* class_escape_sequence occurs inside class_format_directive
1088 and class_invalid_format_directive, so clear it first. */
1089 if (currattr & ATTR_ESCAPE_SEQUENCE)
1091 end_css_class (stream, class_escape_sequence);
1092 currattr &= ~ATTR_ESCAPE_SEQUENCE;
1094 if (~attributes[i] & currattr & ATTR_FORMAT_DIRECTIVE)
1096 end_css_class (stream, class_format_directive);
1097 currattr &= ~ATTR_FORMAT_DIRECTIVE;
1099 else if (~attributes[i] & currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1101 end_css_class (stream, class_invalid_format_directive);
1102 currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE;
1104 if (attributes[i] & ~currattr & ATTR_FORMAT_DIRECTIVE)
1106 begin_css_class (stream, class_format_directive);
1107 currattr |= ATTR_FORMAT_DIRECTIVE;
1109 else if (attributes[i] & ~currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1111 begin_css_class (stream, class_invalid_format_directive);
1112 currattr |= ATTR_INVALID_FORMAT_DIRECTIVE;
1114 /* class_escape_sequence occurs inside class_format_directive
1115 and class_invalid_format_directive, so set it last. */
1116 if (attributes[i] & ~currattr & ATTR_ESCAPE_SEQUENCE)
1118 begin_css_class (stream, class_escape_sequence);
1119 currattr |= ATTR_ESCAPE_SEQUENCE;
1122 ostream_write_mem (stream, &portion[i], 1);
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");
1161 if (fmtdirattr != NULL)
1167 if (conv != (iconv_t)(-1))
1174 print_blank_line (ostream_t stream)
1178 begin_css_class (stream, class_comment);
1179 ostream_write_str (stream, "#\n");
1180 end_css_class (stream, class_comment);
1183 ostream_write_str (stream, "\n");
1188 message_print (const message_ty *mp, ostream_t stream,
1189 const char *charset, size_t page_width, bool blank_line,
1194 /* Separate messages with a blank line. Uniforum doesn't like blank
1195 lines, so use an empty comment (unless there already is one). */
1196 if (blank_line && (!uniforum
1197 || mp->comment == NULL
1198 || mp->comment->nitems == 0
1199 || mp->comment->item[0][0] != '\0'))
1200 print_blank_line (stream);
1203 begin_css_class (stream, class_header);
1204 else if (mp->msgstr[0] == '\0')
1205 begin_css_class (stream, class_untranslated);
1206 else if (mp->is_fuzzy)
1207 begin_css_class (stream, class_fuzzy);
1209 begin_css_class (stream, class_translated);
1211 begin_css_class (stream, class_comment);
1213 /* Print translator comment if available. */
1214 message_print_comment (mp, stream);
1216 /* Print xgettext extracted comments. */
1217 message_print_comment_dot (mp, stream);
1219 /* Print the file position comments. This will help a human who is
1220 trying to navigate the sources. There is no problem of getting
1221 repeated positions, because duplicates are checked for. */
1222 message_print_comment_filepos (mp, stream, uniforum, page_width);
1224 /* Print flag information in special comment. */
1225 message_print_comment_flags (mp, stream, debug);
1227 /* Print the previous msgid. This helps the translator when the msgid has
1228 only slightly changed. */
1229 begin_css_class (stream, class_previous_comment);
1230 if (mp->prev_msgctxt != NULL)
1231 wrap (mp, stream, "#| ", 0, class_previous, "msgctxt", mp->prev_msgctxt,
1232 mp->do_wrap, page_width, charset);
1233 if (mp->prev_msgid != NULL)
1234 wrap (mp, stream, "#| ", 0, class_previous, "msgid", mp->prev_msgid,
1235 mp->do_wrap, page_width, charset);
1236 if (mp->prev_msgid_plural != NULL)
1237 wrap (mp, stream, "#| ", 0, class_previous, "msgid_plural",
1238 mp->prev_msgid_plural, mp->do_wrap, page_width, charset);
1239 end_css_class (stream, class_previous_comment);
1240 extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL
1241 || mp->prev_msgid_plural != NULL
1245 end_css_class (stream, class_comment);
1247 /* Print each of the message components. Wrap them nicely so they
1248 are as readable as possible. If there is no recorded msgstr for
1249 this domain, emit an empty string. */
1250 if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt)
1251 && po_charset_canonicalize (charset) != po_charset_utf8)
1253 char *warning_message =
1255 The following msgctxt contains non-ASCII characters.\n\
1256 This will cause problems to translators who use a character encoding\n\
1257 different from yours. Consider using a pure ASCII msgctxt instead.\n\
1258 %s\n"), mp->msgctxt);
1259 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1260 free (warning_message);
1262 if (!is_ascii_string (mp->msgid)
1263 && po_charset_canonicalize (charset) != po_charset_utf8)
1265 char *warning_message =
1267 The following msgid contains non-ASCII characters.\n\
1268 This will cause problems to translators who use a character encoding\n\
1269 different from yours. Consider using a pure ASCII msgid instead.\n\
1271 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1272 free (warning_message);
1274 if (mp->msgctxt != NULL)
1275 wrap (mp, stream, NULL, extra_indent, class_msgid, "msgctxt", mp->msgctxt,
1276 mp->do_wrap, page_width, charset);
1277 wrap (mp, stream, NULL, extra_indent, class_msgid, "msgid", mp->msgid,
1278 mp->do_wrap, page_width, charset);
1279 if (mp->msgid_plural != NULL)
1280 wrap (mp, stream, NULL, extra_indent, class_msgid, "msgid_plural",
1281 mp->msgid_plural, mp->do_wrap, page_width, charset);
1283 if (mp->msgid_plural == NULL)
1284 wrap (mp, stream, NULL, extra_indent, class_msgstr, "msgstr", mp->msgstr,
1285 mp->do_wrap, page_width, charset);
1288 char prefix_buf[20];
1292 for (p = mp->msgstr, i = 0;
1293 p < mp->msgstr + mp->msgstr_len;
1294 p += strlen (p) + 1, i++)
1296 sprintf (prefix_buf, "msgstr[%u]", i);
1297 wrap (mp, stream, NULL, extra_indent, class_msgstr, prefix_buf, p,
1298 mp->do_wrap, page_width, charset);
1303 end_css_class (stream, class_header);
1304 else if (mp->msgstr[0] == '\0')
1305 end_css_class (stream, class_untranslated);
1306 else if (mp->is_fuzzy)
1307 end_css_class (stream, class_fuzzy);
1309 end_css_class (stream, class_translated);
1314 message_print_obsolete (const message_ty *mp, ostream_t stream,
1315 const char *charset, size_t page_width, bool blank_line)
1319 /* If msgstr is the empty string we print nothing. */
1320 if (mp->msgstr[0] == '\0')
1323 /* Separate messages with a blank line. Uniforum doesn't like blank
1324 lines, so use an empty comment (unless there already is one). */
1326 print_blank_line (stream);
1328 begin_css_class (stream, class_obsolete);
1330 begin_css_class (stream, class_comment);
1332 /* Print translator comment if available. */
1333 message_print_comment (mp, stream);
1335 /* Print xgettext extracted comments (normally empty). */
1336 message_print_comment_dot (mp, stream);
1338 /* Print the file position comments (normally empty). */
1339 message_print_comment_filepos (mp, stream, uniforum, page_width);
1341 /* Print flag information in special comment. */
1346 ostream_write_str (stream, "#,");
1350 ostream_write_str (stream, " fuzzy");
1354 ostream_write_str (stream, "\n");
1357 /* Print the previous msgid. This helps the translator when the msgid has
1358 only slightly changed. */
1359 begin_css_class (stream, class_previous_comment);
1360 if (mp->prev_msgctxt != NULL)
1361 wrap (mp, stream, "#~| ", 0, class_previous, "msgctxt", mp->prev_msgctxt,
1362 mp->do_wrap, page_width, charset);
1363 if (mp->prev_msgid != NULL)
1364 wrap (mp, stream, "#~| ", 0, class_previous, "msgid", mp->prev_msgid,
1365 mp->do_wrap, page_width, charset);
1366 if (mp->prev_msgid_plural != NULL)
1367 wrap (mp, stream, "#~| ", 0, class_previous, "msgid_plural",
1368 mp->prev_msgid_plural, mp->do_wrap, page_width, charset);
1369 end_css_class (stream, class_previous_comment);
1370 extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL
1371 || mp->prev_msgid_plural != NULL
1375 end_css_class (stream, class_comment);
1377 /* Print each of the message components. Wrap them nicely so they
1378 are as readable as possible. */
1379 if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt)
1380 && po_charset_canonicalize (charset) != po_charset_utf8)
1382 char *warning_message =
1384 The following msgctxt contains non-ASCII characters.\n\
1385 This will cause problems to translators who use a character encoding\n\
1386 different from yours. Consider using a pure ASCII msgctxt instead.\n\
1387 %s\n"), mp->msgctxt);
1388 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1389 free (warning_message);
1391 if (!is_ascii_string (mp->msgid)
1392 && po_charset_canonicalize (charset) != po_charset_utf8)
1394 char *warning_message =
1396 The following msgid contains non-ASCII characters.\n\
1397 This will cause problems to translators who use a character encoding\n\
1398 different from yours. Consider using a pure ASCII msgid instead.\n\
1400 po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1401 free (warning_message);
1403 if (mp->msgctxt != NULL)
1404 wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgctxt", mp->msgctxt,
1405 mp->do_wrap, page_width, charset);
1406 wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgid", mp->msgid,
1407 mp->do_wrap, page_width, charset);
1408 if (mp->msgid_plural != NULL)
1409 wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgid_plural",
1410 mp->msgid_plural, mp->do_wrap, page_width, charset);
1412 if (mp->msgid_plural == NULL)
1413 wrap (mp, stream, "#~ ", extra_indent, class_msgstr, "msgstr", mp->msgstr,
1414 mp->do_wrap, page_width, charset);
1417 char prefix_buf[20];
1421 for (p = mp->msgstr, i = 0;
1422 p < mp->msgstr + mp->msgstr_len;
1423 p += strlen (p) + 1, i++)
1425 sprintf (prefix_buf, "msgstr[%u]", i);
1426 wrap (mp, stream, "#~ ", extra_indent, class_msgstr, prefix_buf, p,
1427 mp->do_wrap, page_width, charset);
1431 end_css_class (stream, class_obsolete);
1436 msgdomain_list_print_po (msgdomain_list_ty *mdlp, ostream_t stream,
1437 size_t page_width, bool debug)
1442 /* Write out the messages for each domain. */
1444 for (k = 0; k < mdlp->nitems; k++)
1446 message_list_ty *mlp;
1448 const char *charset;
1449 char *allocated_charset;
1451 /* If the first domain is the default, don't bother emitting
1452 the domain name, because it is the default. */
1454 && strcmp (mdlp->item[k]->domain, MESSAGE_DOMAIN_DEFAULT) == 0))
1457 print_blank_line (stream);
1458 begin_css_class (stream, class_keyword);
1459 ostream_write_str (stream, "domain");
1460 end_css_class (stream, class_keyword);
1461 ostream_write_str (stream, " ");
1462 begin_css_class (stream, class_string);
1463 ostream_write_str (stream, "\"");
1464 begin_css_class (stream, class_text);
1465 ostream_write_str (stream, mdlp->item[k]->domain);
1466 end_css_class (stream, class_text);
1467 ostream_write_str (stream, "\"");
1468 end_css_class (stream, class_string);
1469 ostream_write_str (stream, "\n");
1473 mlp = mdlp->item[k]->messages;
1475 /* Search the header entry. */
1477 for (j = 0; j < mlp->nitems; ++j)
1478 if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1480 header = mlp->item[j]->msgstr;
1484 /* Extract the charset name. */
1486 allocated_charset = NULL;
1489 const char *charsetstr = c_strstr (header, "charset=");
1491 if (charsetstr != NULL)
1495 charsetstr += strlen ("charset=");
1496 len = strcspn (charsetstr, " \t\n");
1497 allocated_charset = (char *) xmalloca (len + 1);
1498 memcpy (allocated_charset, charsetstr, len);
1499 allocated_charset[len] = '\0';
1500 charset = allocated_charset;
1502 /* Treat the dummy default value as if it were absent. */
1503 if (strcmp (charset, "CHARSET") == 0)
1508 /* Write out each of the messages for this domain. */
1509 for (j = 0; j < mlp->nitems; ++j)
1510 if (!mlp->item[j]->obsolete)
1512 message_print (mlp->item[j], stream, charset, page_width,
1517 /* Write out each of the obsolete messages for this domain. */
1518 for (j = 0; j < mlp->nitems; ++j)
1519 if (mlp->item[j]->obsolete)
1521 message_print_obsolete (mlp->item[j], stream, charset, page_width,
1526 if (allocated_charset != NULL)
1527 freea (allocated_charset);
1532 /* Describes a PO file in .po syntax. */
1533 const struct catalog_output_format output_format_po =
1535 msgdomain_list_print_po, /* print */
1536 false, /* requires_utf8 */
1537 true, /* supports_color */
1538 true, /* supports_multiple_domains */
1539 true, /* supports_contexts */
1540 true, /* supports_plurals */
1541 true, /* sorts_obsoletes_to_end */
1542 false, /* alternative_is_po */
1543 false /* alternative_is_java_class */