Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / write-po.c
1 /* GNU gettext - internationalization aids
2    Copyright (C) 1995-1998, 2000-2010, 2012, 2015 Free Software
3    Foundation, Inc.
4
5    This file was written by Peter Miller <millerp@canb.auug.org.au>
6
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.
11
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.
16
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/>.  */
19
20 #ifdef HAVE_CONFIG_H
21 # include <config.h>
22 #endif
23 #include <alloca.h>
24
25 /* Specification.  */
26 #include "write-po.h"
27
28 #include <errno.h>
29 #include <limits.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33
34 #if HAVE_ICONV
35 # include <iconv.h>
36 #endif
37
38 #include "c-ctype.h"
39 #include "po-charset.h"
40 #include "format.h"
41 #include "unilbrk.h"
42 #include "msgl-ascii.h"
43 #include "write-catalog.h"
44 #include "xalloc.h"
45 #include "xmalloca.h"
46 #include "c-strstr.h"
47 #include "ostream.h"
48 #ifdef GETTEXTDATADIR
49 # include "styled-ostream.h"
50 #endif
51 #include "xvasprintf.h"
52 #include "po-xerror.h"
53 #include "gettext.h"
54
55 /* Our regular abbreviation.  */
56 #define _(str) gettext (str)
57
58 #if HAVE_DECL_PUTC_UNLOCKED
59 # undef putc
60 # define putc putc_unlocked
61 #endif
62
63
64 /* =================== Putting together a #, flags line. =================== */
65
66
67 /* Convert IS_FORMAT in the context of programming language LANG to a flag
68    string for use in #, flags.  */
69
70 const char *
71 make_format_description_string (enum is_format is_format, const char *lang,
72                                 bool debug)
73 {
74   static char result[100];
75
76   switch (is_format)
77     {
78     case possible:
79       if (debug)
80         {
81           sprintf (result, "possible-%s-format", lang);
82           break;
83         }
84       /* FALLTHROUGH */
85     case yes_according_to_context:
86     case yes:
87       sprintf (result, "%s-format", lang);
88       break;
89     case no:
90       sprintf (result, "no-%s-format", lang);
91       break;
92     default:
93       /* The others have already been filtered out by significant_format_p.  */
94       abort ();
95     }
96
97   return result;
98 }
99
100
101 /* Return true if IS_FORMAT is worth mentioning in a #, flags list.  */
102
103 bool
104 significant_format_p (enum is_format is_format)
105 {
106   return is_format != undecided && is_format != impossible;
107 }
108
109
110 /* Return true if one of IS_FORMAT is worth mentioning in a #, flags list.  */
111
112 static bool
113 has_significant_format_p (const enum is_format is_format[NFORMATS])
114 {
115   size_t i;
116
117   for (i = 0; i < NFORMATS; i++)
118     if (significant_format_p (is_format[i]))
119       return true;
120   return false;
121 }
122
123
124 /* Convert a RANGE to a freshly allocated string for use in #, flags.  */
125
126 char *
127 make_range_description_string (struct argument_range range)
128 {
129   return xasprintf ("range: %d..%d", range.min, range.max);
130 }
131
132
133 /* Convert a wrapping flag DO_WRAP to a string for use in #, flags.  */
134
135 static const char *
136 make_c_width_description_string (enum is_wrap do_wrap)
137 {
138   const char *result = NULL;
139
140   switch (do_wrap)
141     {
142     case yes:
143       result = "wrap";
144       break;
145     case no:
146       result = "no-wrap";
147       break;
148     default:
149       abort ();
150     }
151
152   return result;
153 }
154
155
156 /* ========================== Styling primitives. ========================== */
157
158
159 /* When compiled in src, enable styling support.
160    When compiled in libgettextpo, don't enable styling support.  */
161 #ifdef GETTEXTDATADIR
162
163 /* Return true if the stream is an instance of styled_ostream_t.  */
164 static inline bool
165 is_stylable (ostream_t stream)
166 {
167   return IS_INSTANCE (stream, ostream, styled_ostream);
168 }
169
170 /* Start a run of text belonging to a given CSS class.  */
171 static void
172 begin_css_class (ostream_t stream, const char *classname)
173 {
174   if (is_stylable (stream))
175     styled_ostream_begin_use_class ((styled_ostream_t) stream, classname);
176 }
177
178 /* End a run of text belonging to a given CSS class.  */
179 static void
180 end_css_class (ostream_t stream, const char *classname)
181 {
182   if (is_stylable (stream))
183     styled_ostream_end_use_class ((styled_ostream_t) stream, classname);
184 }
185
186 #else
187
188 #define is_stylable(stream) false
189 #define begin_css_class(stream,classname) /* empty */
190 #define end_css_class(stream,classname) /* empty */
191
192 #endif
193
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";
200
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";
216
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";
222 #if 0
223 static const char class_added[] = "added";
224 static const char class_changed[] = "changed";
225 static const char class_removed[] = "removed";
226 #endif
227
228 /* Per-character attributes.  */
229 enum
230 {
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
235 };
236
237
238 /* ================ Output parts of a message, as comments. ================ */
239
240
241 /* Output mp->comment as a set of comment lines.  */
242
243 void
244 message_print_comment (const message_ty *mp, ostream_t stream)
245 {
246   if (mp->comment != NULL)
247     {
248       size_t j;
249
250       begin_css_class (stream, class_translator_comment);
251
252       for (j = 0; j < mp->comment->nitems; ++j)
253         {
254           const char *s = mp->comment->item[j];
255           do
256             {
257               const char *e;
258               ostream_write_str (stream, "#");
259               if (*s != '\0')
260                 ostream_write_str (stream, " ");
261               e = strchr (s, '\n');
262               if (e == NULL)
263                 {
264                   ostream_write_str (stream, s);
265                   s = NULL;
266                 }
267               else
268                 {
269                   ostream_write_mem (stream, s, e - s);
270                   s = e + 1;
271                 }
272               ostream_write_str (stream, "\n");
273             }
274           while (s != NULL);
275         }
276
277       end_css_class (stream, class_translator_comment);
278     }
279 }
280
281
282 /* Output mp->comment_dot as a set of comment lines.  */
283
284 void
285 message_print_comment_dot (const message_ty *mp, ostream_t stream)
286 {
287   if (mp->comment_dot != NULL)
288     {
289       size_t j;
290
291       begin_css_class (stream, class_extracted_comment);
292
293       for (j = 0; j < mp->comment_dot->nitems; ++j)
294         {
295           const char *s = mp->comment_dot->item[j];
296           ostream_write_str (stream, "#.");
297           if (*s != '\0')
298             ostream_write_str (stream, " ");
299           ostream_write_str (stream, s);
300           ostream_write_str (stream, "\n");
301         }
302
303       end_css_class (stream, class_extracted_comment);
304     }
305 }
306
307
308 /* Output mp->filepos as a set of comment lines.  */
309
310 static enum filepos_comment_type filepos_comment_type = filepos_comment_full;
311
312 void
313 message_print_comment_filepos (const message_ty *mp, ostream_t stream,
314                                bool uniforum, size_t page_width)
315 {
316   if (filepos_comment_type != filepos_comment_none
317       && mp->filepos_count != 0)
318     {
319       size_t filepos_count;
320       lex_pos_ty *filepos;
321
322       begin_css_class (stream, class_reference_comment);
323
324       if (filepos_comment_type == filepos_comment_file)
325         {
326           size_t i;
327
328           filepos_count = 0;
329           filepos = XNMALLOC (mp->filepos_count, lex_pos_ty);
330
331           for (i = 0; i < mp->filepos_count; ++i)
332             {
333               lex_pos_ty *pp = &mp->filepos[i];
334               size_t j;
335
336               for (j = 0; j < filepos_count; j++)
337                 if (strcmp (filepos[j].file_name, pp->file_name) == 0)
338                   break;
339
340               if (j == filepos_count)
341                 {
342                   filepos[filepos_count].file_name = pp->file_name;
343                   filepos[filepos_count].line_number = (size_t)-1;
344                   filepos_count++;
345                 }
346             }
347         }
348       else
349         {
350           filepos = mp->filepos;
351           filepos_count = mp->filepos_count;
352         }
353
354       if (uniforum)
355         {
356           size_t j;
357
358           for (j = 0; j < filepos_count; ++j)
359             {
360               lex_pos_ty *pp = &filepos[j];
361               const char *cp = pp->file_name;
362               char *str;
363
364               while (cp[0] == '.' && cp[1] == '/')
365                 cp += 2;
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");
375               free (str);
376             }
377         }
378       else
379         {
380           size_t column;
381           size_t j;
382
383           ostream_write_str (stream, "#:");
384           column = 2;
385           for (j = 0; j < filepos_count; ++j)
386             {
387               lex_pos_ty *pp;
388               char buffer[21];
389               const char *cp;
390               size_t len;
391
392               pp = &filepos[j];
393               cp = pp->file_name;
394               while (cp[0] == '.' && cp[1] == '/')
395                 cp += 2;
396               if (filepos_comment_type == filepos_comment_file
397                   /* Some xgettext input formats, like RST, lack line
398                      numbers.  */
399                   || pp->line_number == (size_t)(-1))
400                 buffer[0] = '\0';
401               else
402                 sprintf (buffer, ":%ld", (long) pp->line_number);
403               len = strlen (cp) + strlen (buffer) + 1;
404               if (column > 2 && column + len > page_width)
405                 {
406                   ostream_write_str (stream, "\n#:");
407                   column = 2;
408                 }
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);
414               column += len;
415             }
416           ostream_write_str (stream, "\n");
417         }
418
419       if (filepos != mp->filepos)
420         free (filepos);
421
422       end_css_class (stream, class_reference_comment);
423     }
424 }
425
426
427 /* Output mp->is_fuzzy, mp->is_format, mp->range, mp->do_wrap as a comment
428    line.  */
429
430 void
431 message_print_comment_flags (const message_ty *mp, ostream_t stream, bool debug)
432 {
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)
437     {
438       bool first_flag = true;
439       size_t i;
440
441       begin_css_class (stream, class_flag_comment);
442
443       ostream_write_str (stream, "#,");
444
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
447          output.  */
448       if (mp->is_fuzzy && mp->msgstr[0] != '\0')
449         {
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);
456           first_flag = false;
457         }
458
459       for (i = 0; i < NFORMATS; i++)
460         if (significant_format_p (mp->is_format[i]))
461           {
462             if (!first_flag)
463               ostream_write_str (stream, ",");
464
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],
469                                                                format_language[i],
470                                                                debug));
471             end_css_class (stream, class_flag);
472             first_flag = false;
473           }
474
475       if (has_range_p (mp->range))
476         {
477           char *string;
478
479           if (!first_flag)
480             ostream_write_str (stream, ",");
481
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);
486           free (string);
487           end_css_class (stream, class_flag);
488           first_flag = false;
489         }
490
491       if (mp->do_wrap == no)
492         {
493           if (!first_flag)
494             ostream_write_str (stream, ",");
495
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);
501           first_flag = false;
502         }
503
504       ostream_write_str (stream, "\n");
505
506       end_css_class (stream, class_flag_comment);
507     }
508 }
509
510
511 /* ========= Some parameters for use by 'msgdomain_list_print_po'. ========= */
512
513
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;
518
519 void
520 message_page_width_ignore ()
521 {
522   wrap_strings = false;
523 }
524
525
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;
531
532 void
533 message_print_style_indent ()
534 {
535   indent = true;
536 }
537
538 void
539 message_print_style_uniforum ()
540 {
541   uniforum = true;
542 }
543
544 void
545 message_print_style_escape (bool flag)
546 {
547   escape = flag;
548 }
549
550 void
551 message_print_style_filepos (enum filepos_comment_type type)
552 {
553   filepos_comment_type = type;
554 }
555
556
557 /* --add-location argument handling.  Return an error indicator.  */
558 bool
559 handle_filepos_comment_option (const char *option)
560 {
561   if (option != NULL)
562     {
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);
569       else
570         {
571           fprintf (stderr, "invalid --add-location argument: %s\n", option);
572           return true;
573         }
574     }
575   else
576     /* --add-location is equivalent to --add-location=full.  */
577     message_print_style_filepos (filepos_comment_full);
578   return false;
579 }
580
581
582 /* =============== msgdomain_list_print_po() and subroutines. =============== */
583
584
585 /* A version of memcpy optimized for the case n <= 1.  */
586 static inline void
587 memcpy_small (void *dst, const void *src, size_t n)
588 {
589   if (n > 0)
590     {
591       char *q = (char *) dst;
592       const char *p = (const char *) src;
593
594       *q = *p;
595       if (--n > 0)
596         do *++q = *++p; while (--n > 0);
597     }
598 }
599
600
601 /* A version of memset optimized for the case n <= 1.  */
602 static inline void
603 memset_small (void *dst, char c, size_t n)
604 {
605   if (n > 0)
606     {
607       char *p = (char *) dst;
608
609       *p = c;
610       if (--n > 0)
611         do *++p = c; while (--n > 0);
612     }
613 }
614
615
616 static void
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,
621       const char *charset)
622 {
623   const char *canon_charset;
624   char *fmtdir;
625   char *fmtdirattr;
626   const char *s;
627   bool first_line;
628 #if HAVE_ICONV
629   const char *envval;
630   iconv_t conv;
631 #endif
632   bool weird_cjk;
633
634   canon_charset = po_charset_canonicalize (charset);
635
636 #if HAVE_ICONV
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);
647   else
648     if (canon_charset == NULL)
649       /* Invalid PO file encoding.  */
650       conv = (iconv_t)(-1);
651     else
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);
657       else
658 # endif
659       /* Avoid Solaris 2.9 bug with GB2312, EUC-TW, BIG5, BIG5-HKSCS, GBK,
660          GB18030.  */
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);
669       else
670 # endif
671       /* Use iconv() to parse multibyte characters.  */
672       conv = iconv_open ("UTF-8", canon_charset);
673
674   if (conv != (iconv_t)(-1))
675     weird_cjk = false;
676   else
677 #endif
678     if (canon_charset == NULL)
679       weird_cjk = false;
680     else
681       weird_cjk = po_is_charset_weird_cjk (canon_charset);
682
683   if (canon_charset == NULL)
684     canon_charset = po_charset_ascii;
685
686   /* Determine the extent of format string directives.  */
687   fmtdir = NULL;
688   fmtdirattr = NULL;
689   if (value[0] != '\0')
690     {
691       bool is_msgstr =
692         (strlen (name) >= 6 && memcmp (name, "msgstr", 6) == 0);
693         /* or equivalent: = (css_class == class_msgstr) */
694       size_t i;
695
696       for (i = 0; i < NFORMATS; i++)
697         if (possible_format_p (mp->is_format[i]))
698           {
699             size_t len = strlen (value);
700             struct formatstring_parser *parser = formatstring_parsers[i];
701             char *invalid_reason = NULL;
702             void *descr;
703             const char *fdp;
704             const char *fd_end;
705             char *fdap;
706
707             fmtdir = XCALLOC (len, char);
708             descr = parser->parse (value, is_msgstr, fmtdir, &invalid_reason);
709             if (descr != NULL)
710               parser->free (descr);
711
712             /* Locate the FMTDIR_* bits and transform the array to an array
713                of attributes.  */
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)
718                 {
719                   const char *fdq;
720                   for (fdq = fdp; fdq < fd_end; fdq++)
721                     if (*fdq & (FMTDIR_END | FMTDIR_ERROR))
722                       break;
723                   if (!(fdq < fd_end))
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.  */
727                     abort ();
728                   if (*fdq & FMTDIR_ERROR)
729                     memset (fdap, ATTR_INVALID_FORMAT_DIRECTIVE, fdq - fdp + 1);
730                   else
731                     memset (fdap, ATTR_FORMAT_DIRECTIVE, fdq - fdp + 1);
732                   fdap += fdq - fdp;
733                   fdp = fdq;
734                 }
735               else
736                 *fdap = 0;
737
738             break;
739           }
740     }
741
742   /* Loop over the '\n' delimited portions of value.  */
743   s = value;
744   first_line = true;
745   do
746     {
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')
751
752       const char *es;
753       const char *ep;
754       size_t portion_len;
755       char *portion;
756       char *overrides;
757       char *attributes;
758       char *linebreaks;
759       char *pp;
760       char *op;
761       char *ap;
762       int startcol, startcol_after_break, width;
763       size_t i;
764
765       for (es = s; *es != '\0'; )
766         if (*es++ == '\n')
767           break;
768
769       /* Expand escape sequences in each portion.  */
770       for (ep = s, portion_len = 0; ep < es; ep++)
771         {
772           char c = *ep;
773           if (is_escape (c))
774             portion_len += 2;
775           else if (escape && !c_isprint ((unsigned char) c))
776             portion_len += 4;
777           else if (c == '\\' || c == '"')
778             portion_len += 2;
779           else
780             {
781 #if HAVE_ICONV
782               if (conv != (iconv_t)(-1))
783                 {
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.  */
788                   char scratchbuf[64];
789                   const char *inptr = ep;
790                   size_t insize;
791                   char *outptr = &scratchbuf[0];
792                   size_t outsize = sizeof (scratchbuf);
793                   size_t res;
794
795                   res = (size_t)(-1);
796                   for (insize = 1; inptr + insize <= es; insize++)
797                     {
798                       res = iconv (conv,
799                                    (ICONV_CONST char **) &inptr, &insize,
800                                    &outptr, &outsize);
801                       if (!(res == (size_t)(-1) && errno == EINVAL))
802                         break;
803                       /* We expect that no input bytes have been consumed
804                          so far.  */
805                       if (inptr != ep)
806                         abort ();
807                     }
808                   if (res == (size_t)(-1))
809                     {
810                       if (errno == EILSEQ)
811                         {
812                           po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
813                                      _("invalid multibyte sequence"));
814                           continue;
815                         }
816                       else if (errno == EINVAL)
817                         {
818                           /* This could happen if an incomplete
819                              multibyte sequence at the end of input
820                              bytes.  */
821                           po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
822                                      _("incomplete multibyte sequence"));
823                           continue;
824                         }
825                       else
826                         abort ();
827                     }
828                   insize = inptr - ep;
829                   portion_len += insize;
830                   ep += insize - 1;
831                 }
832               else
833 #endif
834                 {
835                   if (weird_cjk
836                       /* Special handling of encodings with CJK structure.  */
837                       && ep + 2 <= es
838                       && (unsigned char) ep[0] >= 0x80
839                       && (unsigned char) ep[1] >= 0x30)
840                     {
841                       portion_len += 2;
842                       ep += 1;
843                     }
844                   else
845                     portion_len += 1;
846                 }
847             }
848         }
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++)
853         {
854           char c = *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;
861           if (is_escape (c))
862             {
863               switch (c)
864                 {
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;
872                 default: abort ();
873                 }
874               *pp++ = '\\';
875               *pp++ = c;
876               *op++ = brk;
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
881                  '\n' and '\t'.  */
882               if (c != 'n' && c != 't')
883                 {
884                   char *error_message =
885                     xasprintf (_("\
886 internationalized messages should not contain the '\\%c' escape sequence"),
887                                c);
888                   po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, false,
889                              error_message);
890                   free (error_message);
891                 }
892             }
893           else if (escape && !c_isprint ((unsigned char) c))
894             {
895               *pp++ = '\\';
896               *pp++ = '0' + (((unsigned char) c >> 6) & 7);
897               *pp++ = '0' + (((unsigned char) c >> 3) & 7);
898               *pp++ = '0' + ((unsigned char) c & 7);
899               *op++ = brk;
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;
907             }
908           else if (c == '\\' || c == '"')
909             {
910               *pp++ = '\\';
911               *pp++ = c;
912               *op++ = brk;
913               *op++ = UC_BREAK_PROHIBITED;
914               *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
915               *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
916             }
917           else
918             {
919 #if HAVE_ICONV
920               if (conv != (iconv_t)(-1))
921                 {
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.  */
926                   char scratchbuf[64];
927                   const char *inptr = ep;
928                   size_t insize;
929                   char *outptr = &scratchbuf[0];
930                   size_t outsize = sizeof (scratchbuf);
931                   size_t res;
932
933                   res = (size_t)(-1);
934                   for (insize = 1; inptr + insize <= es; insize++)
935                     {
936                       res = iconv (conv,
937                                    (ICONV_CONST char **) &inptr, &insize,
938                                    &outptr, &outsize);
939                       if (!(res == (size_t)(-1) && errno == EINVAL))
940                         break;
941                       /* We expect that no input bytes have been consumed
942                          so far.  */
943                       if (inptr != ep)
944                         abort ();
945                     }
946                   if (res == (size_t)(-1))
947                     {
948                       if (errno == EILSEQ)
949                         {
950                           po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0,
951                                      false, _("invalid multibyte sequence"));
952                           continue;
953                         }
954                       else
955                         abort ();
956                     }
957                   insize = inptr - ep;
958                   memcpy_small (pp, ep, insize);
959                   pp += insize;
960                   *op = brk;
961                   memset_small (op + 1, UC_BREAK_PROHIBITED, insize - 1);
962                   op += insize;
963                   memset_small (ap, attr, insize);
964                   ap += insize;
965                   ep += insize - 1;
966                 }
967               else
968 #endif
969                 {
970                   if (weird_cjk
971                       /* Special handling of encodings with CJK structure.  */
972                       && ep + 2 <= es
973                       && (unsigned char) c >= 0x80
974                       && (unsigned char) ep[1] >= 0x30)
975                     {
976                       *pp++ = c;
977                       ep += 1;
978                       *pp++ = *ep;
979                       *op++ = brk;
980                       *op++ = UC_BREAK_PROHIBITED;
981                       *ap++ = attr;
982                       *ap++ = attr;
983                     }
984                   else
985                     {
986                       *pp++ = c;
987                       *op++ = brk;
988                       *ap++ = attr;
989                     }
990                 }
991             }
992         }
993
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;
997
998       linebreaks = XNMALLOC (portion_len, char);
999
1000       /* Subsequent lines after a break are all indented.
1001          See INDENT-S.  */
1002       startcol_after_break = (line_prefix ? strlen (line_prefix) : 0);
1003       if (indent)
1004         startcol_after_break = (startcol_after_break + extra_indent + 8) & ~7;
1005       startcol_after_break++;
1006
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;
1011
1012     recompute:
1013       /* The line starts with different things depending on whether it
1014          is the first line, and if we are using the indented style.
1015          See INDENT-F.  */
1016       startcol = (line_prefix ? strlen (line_prefix) : 0);
1017       if (first_line)
1018         {
1019           startcol += strlen (name);
1020           if (indent)
1021             startcol = (startcol + extra_indent + 8) & ~7;
1022           else
1023             startcol++;
1024         }
1025       else
1026         {
1027           if (indent)
1028             startcol = (startcol + extra_indent + 8) & ~7;
1029         }
1030       /* Allow room for the opening quote character.  */
1031       startcol++;
1032       /* Adjust for indentation of subsequent lines.  */
1033       startcol -= startcol_after_break;
1034
1035       /* Do line breaking on the portion.  */
1036       ulc_width_linebreaks (portion, portion_len, width, startcol, 0,
1037                             overrides, canon_charset, linebreaks);
1038
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
1041          and restart.  */
1042       if (first_line && !indent
1043           && portion_len > 0
1044           && (*es != '\0'
1045               || startcol > width
1046               || memchr (linebreaks, UC_BREAK_POSSIBLE, portion_len) != NULL))
1047         {
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");
1060           first_line = false;
1061           /* Recompute startcol and linebreaks.  */
1062           goto recompute;
1063         }
1064
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
1067          used.  INDENT-F.  */
1068       {
1069         int currcol = 0;
1070
1071         if (line_prefix != NULL)
1072           {
1073             ostream_write_str (stream, line_prefix);
1074             currcol = strlen (line_prefix);
1075           }
1076         begin_css_class (stream, css_class);
1077         if (first_line)
1078           {
1079             begin_css_class (stream, class_keyword);
1080             ostream_write_str (stream, name);
1081             currcol += strlen (name);
1082             end_css_class (stream, class_keyword);
1083             if (indent)
1084               {
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;
1090               }
1091             else
1092               {
1093                 ostream_write_str (stream, " ");
1094                 currcol++;
1095               }
1096             first_line = false;
1097           }
1098         else
1099           {
1100             if (indent)
1101               {
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;
1107               }
1108           }
1109       }
1110
1111       /* Print the portion itself, with linebreaks where necessary.  */
1112       {
1113         char currattr = 0;
1114
1115         begin_css_class (stream, class_string);
1116         ostream_write_str (stream, "\"");
1117         begin_css_class (stream, class_text);
1118
1119         for (i = 0; i < portion_len; i++)
1120           {
1121             if (linebreaks[i] == UC_BREAK_POSSIBLE)
1122               {
1123                 int currcol;
1124
1125                 /* Change currattr so that it becomes 0.  */
1126                 if (currattr & ATTR_ESCAPE_SEQUENCE)
1127                   {
1128                     end_css_class (stream, class_escape_sequence);
1129                     currattr &= ~ATTR_ESCAPE_SEQUENCE;
1130                   }
1131                 if (currattr & ATTR_FORMAT_DIRECTIVE)
1132                   {
1133                     end_css_class (stream, class_format_directive);
1134                     currattr &= ~ATTR_FORMAT_DIRECTIVE;
1135                   }
1136                 else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1137                   {
1138                     end_css_class (stream, class_invalid_format_directive);
1139                     currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE;
1140                   }
1141                 if (!(currattr == 0))
1142                   abort ();
1143
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");
1149                 currcol = 0;
1150                 /* INDENT-S.  */
1151                 if (line_prefix != NULL)
1152                   {
1153                     ostream_write_str (stream, line_prefix);
1154                     currcol = strlen (line_prefix);
1155                   }
1156                 begin_css_class (stream, css_class);
1157                 if (indent)
1158                   {
1159                     ostream_write_mem (stream, "        ", 8 - (currcol & 7));
1160                     currcol = (currcol + 8) & ~7;
1161                   }
1162                 begin_css_class (stream, class_string);
1163                 ostream_write_str (stream, "\"");
1164                 begin_css_class (stream, class_text);
1165               }
1166             /* Change currattr so that it matches attributes[i].  */
1167             if (attributes[i] != currattr)
1168               {
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)
1172                   {
1173                     end_css_class (stream, class_escape_sequence);
1174                     currattr &= ~ATTR_ESCAPE_SEQUENCE;
1175                   }
1176                 if (~attributes[i] & currattr & ATTR_FORMAT_DIRECTIVE)
1177                   {
1178                     end_css_class (stream, class_format_directive);
1179                     currattr &= ~ATTR_FORMAT_DIRECTIVE;
1180                   }
1181                 else if (~attributes[i] & currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1182                   {
1183                     end_css_class (stream, class_invalid_format_directive);
1184                     currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE;
1185                   }
1186                 if (attributes[i] & ~currattr & ATTR_FORMAT_DIRECTIVE)
1187                   {
1188                     begin_css_class (stream, class_format_directive);
1189                     currattr |= ATTR_FORMAT_DIRECTIVE;
1190                   }
1191                 else if (attributes[i] & ~currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1192                   {
1193                     begin_css_class (stream, class_invalid_format_directive);
1194                     currattr |= ATTR_INVALID_FORMAT_DIRECTIVE;
1195                   }
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)
1199                   {
1200                     begin_css_class (stream, class_escape_sequence);
1201                     currattr |= ATTR_ESCAPE_SEQUENCE;
1202                   }
1203               }
1204             ostream_write_mem (stream, &portion[i], 1);
1205           }
1206
1207         /* Change currattr so that it becomes 0.  */
1208         if (currattr & ATTR_ESCAPE_SEQUENCE)
1209           {
1210             end_css_class (stream, class_escape_sequence);
1211             currattr &= ~ATTR_ESCAPE_SEQUENCE;
1212           }
1213         if (currattr & ATTR_FORMAT_DIRECTIVE)
1214           {
1215             end_css_class (stream, class_format_directive);
1216             currattr &= ~ATTR_FORMAT_DIRECTIVE;
1217           }
1218         else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1219           {
1220             end_css_class (stream, class_invalid_format_directive);
1221             currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE;
1222           }
1223         if (!(currattr == 0))
1224           abort ();
1225
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");
1231       }
1232
1233       free (linebreaks);
1234       free (attributes);
1235       free (overrides);
1236       free (portion);
1237
1238       s = es;
1239 #     undef is_escape
1240     }
1241   while (*s);
1242
1243   if (fmtdirattr != NULL)
1244     free (fmtdirattr);
1245   if (fmtdir != NULL)
1246     free (fmtdir);
1247
1248 #if HAVE_ICONV
1249   if (conv != (iconv_t)(-1))
1250     iconv_close (conv);
1251 #endif
1252 }
1253
1254
1255 static void
1256 print_blank_line (ostream_t stream)
1257 {
1258   if (uniforum)
1259     {
1260       begin_css_class (stream, class_comment);
1261       ostream_write_str (stream, "#\n");
1262       end_css_class (stream, class_comment);
1263     }
1264   else
1265     ostream_write_str (stream, "\n");
1266 }
1267
1268
1269 static void
1270 message_print (const message_ty *mp, ostream_t stream,
1271                const char *charset, size_t page_width, bool blank_line,
1272                bool debug)
1273 {
1274   int extra_indent;
1275
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);
1283
1284   if (is_header (mp))
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);
1290   else
1291     begin_css_class (stream, class_translated);
1292
1293   begin_css_class (stream, class_comment);
1294
1295   /* Print translator comment if available.  */
1296   message_print_comment (mp, stream);
1297
1298   /* Print xgettext extracted comments.  */
1299   message_print_comment_dot (mp, stream);
1300
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);
1305
1306   /* Print flag information in special comment.  */
1307   message_print_comment_flags (mp, stream, debug);
1308
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
1324                   ? 3
1325                   : 0);
1326
1327   end_css_class (stream, class_comment);
1328
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)
1334     {
1335       char *warning_message =
1336         xasprintf (_("\
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);
1343     }
1344   if (!is_ascii_string (mp->msgid)
1345       && po_charset_canonicalize (charset) != po_charset_utf8)
1346     {
1347       char *warning_message =
1348         xasprintf (_("\
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\
1352 %s\n"), mp->msgid);
1353       po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1354       free (warning_message);
1355     }
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);
1364
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);
1368   else
1369     {
1370       char prefix_buf[20];
1371       unsigned int i;
1372       const char *p;
1373
1374       for (p = mp->msgstr, i = 0;
1375            p < mp->msgstr + mp->msgstr_len;
1376            p += strlen (p) + 1, i++)
1377         {
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);
1381         }
1382     }
1383
1384   if (is_header (mp))
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);
1390   else
1391     end_css_class (stream, class_translated);
1392 }
1393
1394
1395 static void
1396 message_print_obsolete (const message_ty *mp, ostream_t stream,
1397                         const char *charset, size_t page_width, bool blank_line)
1398 {
1399   int extra_indent;
1400
1401   /* If msgstr is the empty string we print nothing.  */
1402   if (mp->msgstr[0] == '\0')
1403     return;
1404
1405   /* Separate messages with a blank line.  Uniforum doesn't like blank
1406      lines, so use an empty comment (unless there already is one).  */
1407   if (blank_line)
1408     print_blank_line (stream);
1409
1410   begin_css_class (stream, class_obsolete);
1411
1412   begin_css_class (stream, class_comment);
1413
1414   /* Print translator comment if available.  */
1415   message_print_comment (mp, stream);
1416
1417   /* Print xgettext extracted comments (normally empty).  */
1418   message_print_comment_dot (mp, stream);
1419
1420   /* Print the file position comments (normally empty).  */
1421   message_print_comment_filepos (mp, stream, uniforum, page_width);
1422
1423   /* Print flag information in special comment.  */
1424   if (mp->is_fuzzy)
1425     {
1426       ostream_write_str (stream, "#,");
1427
1428       if (mp->is_fuzzy)
1429         ostream_write_str (stream, " fuzzy");
1430
1431       ostream_write_str (stream, "\n");
1432     }
1433
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
1449                   ? 1
1450                   : 0);
1451
1452   end_css_class (stream, class_comment);
1453
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)
1458     {
1459       char *warning_message =
1460         xasprintf (_("\
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);
1467     }
1468   if (!is_ascii_string (mp->msgid)
1469       && po_charset_canonicalize (charset) != po_charset_utf8)
1470     {
1471       char *warning_message =
1472         xasprintf (_("\
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\
1476 %s\n"), mp->msgid);
1477       po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1478       free (warning_message);
1479     }
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);
1488
1489   if (mp->msgid_plural == NULL)
1490     wrap (mp, stream, "#~ ", extra_indent, class_msgstr, "msgstr", mp->msgstr,
1491           mp->do_wrap, page_width, charset);
1492   else
1493     {
1494       char prefix_buf[20];
1495       unsigned int i;
1496       const char *p;
1497
1498       for (p = mp->msgstr, i = 0;
1499            p < mp->msgstr + mp->msgstr_len;
1500            p += strlen (p) + 1, i++)
1501         {
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);
1505         }
1506     }
1507
1508   end_css_class (stream, class_obsolete);
1509 }
1510
1511
1512 static void
1513 msgdomain_list_print_po (msgdomain_list_ty *mdlp, ostream_t stream,
1514                          size_t page_width, bool debug)
1515 {
1516   size_t j, k;
1517   bool blank_line;
1518
1519   /* Write out the messages for each domain.  */
1520   blank_line = false;
1521   for (k = 0; k < mdlp->nitems; k++)
1522     {
1523       message_list_ty *mlp;
1524       const char *header;
1525       const char *charset;
1526       char *allocated_charset;
1527
1528       /* If the first domain is the default, don't bother emitting
1529          the domain name, because it is the default.  */
1530       if (!(k == 0
1531             && strcmp (mdlp->item[k]->domain, MESSAGE_DOMAIN_DEFAULT) == 0))
1532         {
1533           if (blank_line)
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");
1547           blank_line = true;
1548         }
1549
1550       mlp = mdlp->item[k]->messages;
1551
1552       /* Search the header entry.  */
1553       header = NULL;
1554       for (j = 0; j < mlp->nitems; ++j)
1555         if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1556           {
1557             header = mlp->item[j]->msgstr;
1558             break;
1559           }
1560
1561       /* Extract the charset name.  */
1562       charset = "ASCII";
1563       allocated_charset = NULL;
1564       if (header != NULL)
1565         {
1566           const char *charsetstr = c_strstr (header, "charset=");
1567
1568           if (charsetstr != NULL)
1569             {
1570               size_t len;
1571
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;
1578
1579               /* Treat the dummy default value as if it were absent.  */
1580               if (strcmp (charset, "CHARSET") == 0)
1581                 charset = "ASCII";
1582             }
1583         }
1584
1585       /* Write out each of the messages for this domain.  */
1586       for (j = 0; j < mlp->nitems; ++j)
1587         if (!mlp->item[j]->obsolete)
1588           {
1589             message_print (mlp->item[j], stream, charset, page_width,
1590                            blank_line, debug);
1591             blank_line = true;
1592           }
1593
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)
1597           {
1598             message_print_obsolete (mlp->item[j], stream, charset, page_width,
1599                                     blank_line);
1600             blank_line = true;
1601           }
1602
1603       if (allocated_charset != NULL)
1604         freea (allocated_charset);
1605     }
1606 }
1607
1608
1609 /* Describes a PO file in .po syntax.  */
1610 const struct catalog_output_format output_format_po =
1611 {
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 */
1621 };