• Home
  • History
  • Annotate
  • Line#
  • Navigate
  • Raw
  • Download
  • only in /netgear-WNDR4500v2-V1.0.0.60_1.0.38/ap/gpl/timemachine/gettext-0.17/gettext-tools/src/
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