xref: /netbsd-src/external/gpl2/gettext/dist/gettext-tools/src/write-po.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
1*946379e7Schristos /* GNU gettext - internationalization aids
2*946379e7Schristos    Copyright (C) 1995-1998, 2000-2006 Free Software Foundation, Inc.
3*946379e7Schristos 
4*946379e7Schristos    This file was written by Peter Miller <millerp@canb.auug.org.au>
5*946379e7Schristos 
6*946379e7Schristos    This program is free software; you can redistribute it and/or modify
7*946379e7Schristos    it under the terms of the GNU General Public License as published by
8*946379e7Schristos    the Free Software Foundation; either version 2, or (at your option)
9*946379e7Schristos    any later version.
10*946379e7Schristos 
11*946379e7Schristos    This program is distributed in the hope that it will be useful,
12*946379e7Schristos    but WITHOUT ANY WARRANTY; without even the implied warranty of
13*946379e7Schristos    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14*946379e7Schristos    GNU General Public License for more details.
15*946379e7Schristos 
16*946379e7Schristos    You should have received a copy of the GNU General Public License
17*946379e7Schristos    along with this program; if not, write to the Free Software Foundation,
18*946379e7Schristos    Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
19*946379e7Schristos 
20*946379e7Schristos #ifdef HAVE_CONFIG_H
21*946379e7Schristos # include <config.h>
22*946379e7Schristos #endif
23*946379e7Schristos #include <alloca.h>
24*946379e7Schristos 
25*946379e7Schristos /* Specification.  */
26*946379e7Schristos #include "write-po.h"
27*946379e7Schristos 
28*946379e7Schristos #include <errno.h>
29*946379e7Schristos #include <limits.h>
30*946379e7Schristos #include <stdio.h>
31*946379e7Schristos #include <stdlib.h>
32*946379e7Schristos #include <string.h>
33*946379e7Schristos 
34*946379e7Schristos #if HAVE_ICONV
35*946379e7Schristos # include <iconv.h>
36*946379e7Schristos #endif
37*946379e7Schristos 
38*946379e7Schristos #include "c-ctype.h"
39*946379e7Schristos #include "po-charset.h"
40*946379e7Schristos #include "linebreak.h"
41*946379e7Schristos #include "msgl-ascii.h"
42*946379e7Schristos #include "write-properties.h"
43*946379e7Schristos #include "write-stringtable.h"
44*946379e7Schristos #include "xalloc.h"
45*946379e7Schristos #include "xallocsa.h"
46*946379e7Schristos #include "c-strstr.h"
47*946379e7Schristos #include "xvasprintf.h"
48*946379e7Schristos #include "po-xerror.h"
49*946379e7Schristos #include "gettext.h"
50*946379e7Schristos 
51*946379e7Schristos /* Our regular abbreviation.  */
52*946379e7Schristos #define _(str) gettext (str)
53*946379e7Schristos 
54*946379e7Schristos #if HAVE_DECL_PUTC_UNLOCKED
55*946379e7Schristos # undef putc
56*946379e7Schristos # define putc putc_unlocked
57*946379e7Schristos #endif
58*946379e7Schristos 
59*946379e7Schristos 
60*946379e7Schristos /* =================== Putting together a #, flags line. =================== */
61*946379e7Schristos 
62*946379e7Schristos 
63*946379e7Schristos /* Convert IS_FORMAT in the context of programming language LANG to a flag
64*946379e7Schristos    string for use in #, flags.  */
65*946379e7Schristos 
66*946379e7Schristos const char *
make_format_description_string(enum is_format is_format,const char * lang,bool debug)67*946379e7Schristos make_format_description_string (enum is_format is_format, const char *lang,
68*946379e7Schristos 				bool debug)
69*946379e7Schristos {
70*946379e7Schristos   static char result[100];
71*946379e7Schristos 
72*946379e7Schristos   switch (is_format)
73*946379e7Schristos     {
74*946379e7Schristos     case possible:
75*946379e7Schristos       if (debug)
76*946379e7Schristos 	{
77*946379e7Schristos 	  sprintf (result, " possible-%s-format", lang);
78*946379e7Schristos 	  break;
79*946379e7Schristos 	}
80*946379e7Schristos       /* FALLTHROUGH */
81*946379e7Schristos     case yes_according_to_context:
82*946379e7Schristos     case yes:
83*946379e7Schristos       sprintf (result, " %s-format", lang);
84*946379e7Schristos       break;
85*946379e7Schristos     case no:
86*946379e7Schristos       sprintf (result, " no-%s-format", lang);
87*946379e7Schristos       break;
88*946379e7Schristos     default:
89*946379e7Schristos       /* The others have already been filtered out by significant_format_p.  */
90*946379e7Schristos       abort ();
91*946379e7Schristos     }
92*946379e7Schristos 
93*946379e7Schristos   return result;
94*946379e7Schristos }
95*946379e7Schristos 
96*946379e7Schristos 
97*946379e7Schristos /* Return true if IS_FORMAT is worth mentioning in a #, flags list.  */
98*946379e7Schristos 
99*946379e7Schristos bool
significant_format_p(enum is_format is_format)100*946379e7Schristos significant_format_p (enum is_format is_format)
101*946379e7Schristos {
102*946379e7Schristos   return is_format != undecided && is_format != impossible;
103*946379e7Schristos }
104*946379e7Schristos 
105*946379e7Schristos 
106*946379e7Schristos /* Return true if one of IS_FORMAT is worth mentioning in a #, flags list.  */
107*946379e7Schristos 
108*946379e7Schristos static bool
has_significant_format_p(const enum is_format is_format[NFORMATS])109*946379e7Schristos has_significant_format_p (const enum is_format is_format[NFORMATS])
110*946379e7Schristos {
111*946379e7Schristos   size_t i;
112*946379e7Schristos 
113*946379e7Schristos   for (i = 0; i < NFORMATS; i++)
114*946379e7Schristos     if (significant_format_p (is_format[i]))
115*946379e7Schristos       return true;
116*946379e7Schristos   return false;
117*946379e7Schristos }
118*946379e7Schristos 
119*946379e7Schristos 
120*946379e7Schristos /* Convert a wrapping flag DO_WRAP to a string for use in #, flags.  */
121*946379e7Schristos 
122*946379e7Schristos static const char *
make_c_width_description_string(enum is_wrap do_wrap)123*946379e7Schristos make_c_width_description_string (enum is_wrap do_wrap)
124*946379e7Schristos {
125*946379e7Schristos   const char *result = NULL;
126*946379e7Schristos 
127*946379e7Schristos   switch (do_wrap)
128*946379e7Schristos     {
129*946379e7Schristos     case yes:
130*946379e7Schristos       result = " wrap";
131*946379e7Schristos       break;
132*946379e7Schristos     case no:
133*946379e7Schristos       result = " no-wrap";
134*946379e7Schristos       break;
135*946379e7Schristos     default:
136*946379e7Schristos       abort ();
137*946379e7Schristos     }
138*946379e7Schristos 
139*946379e7Schristos   return result;
140*946379e7Schristos }
141*946379e7Schristos 
142*946379e7Schristos 
143*946379e7Schristos /* ================ Output parts of a message, as comments. ================ */
144*946379e7Schristos 
145*946379e7Schristos 
146*946379e7Schristos /* Output mp->comment as a set of comment lines.  */
147*946379e7Schristos 
148*946379e7Schristos void
message_print_comment(const message_ty * mp,FILE * fp)149*946379e7Schristos message_print_comment (const message_ty *mp, FILE *fp)
150*946379e7Schristos {
151*946379e7Schristos   if (mp->comment != NULL)
152*946379e7Schristos     {
153*946379e7Schristos       size_t j;
154*946379e7Schristos 
155*946379e7Schristos       for (j = 0; j < mp->comment->nitems; ++j)
156*946379e7Schristos 	{
157*946379e7Schristos 	  const char *s = mp->comment->item[j];
158*946379e7Schristos 	  do
159*946379e7Schristos 	    {
160*946379e7Schristos 	      const char *e;
161*946379e7Schristos 	      putc ('#', fp);
162*946379e7Schristos 	      if (*s != '\0' && *s != ' ')
163*946379e7Schristos 		putc (' ', fp);
164*946379e7Schristos 	      e = strchr (s, '\n');
165*946379e7Schristos 	      if (e == NULL)
166*946379e7Schristos 		{
167*946379e7Schristos 		  fputs (s, fp);
168*946379e7Schristos 		  s = NULL;
169*946379e7Schristos 		}
170*946379e7Schristos 	      else
171*946379e7Schristos 		{
172*946379e7Schristos 		  fwrite (s, 1, e - s, fp);
173*946379e7Schristos 		  s = e + 1;
174*946379e7Schristos 		}
175*946379e7Schristos 	      putc ('\n', fp);
176*946379e7Schristos 	    }
177*946379e7Schristos 	  while (s != NULL);
178*946379e7Schristos 	}
179*946379e7Schristos     }
180*946379e7Schristos }
181*946379e7Schristos 
182*946379e7Schristos 
183*946379e7Schristos /* Output mp->comment_dot as a set of comment lines.  */
184*946379e7Schristos 
185*946379e7Schristos void
message_print_comment_dot(const message_ty * mp,FILE * fp)186*946379e7Schristos message_print_comment_dot (const message_ty *mp, FILE *fp)
187*946379e7Schristos {
188*946379e7Schristos   if (mp->comment_dot != NULL)
189*946379e7Schristos     {
190*946379e7Schristos       size_t j;
191*946379e7Schristos 
192*946379e7Schristos       for (j = 0; j < mp->comment_dot->nitems; ++j)
193*946379e7Schristos 	{
194*946379e7Schristos 	  const char *s = mp->comment_dot->item[j];
195*946379e7Schristos 	  putc ('#', fp);
196*946379e7Schristos 	  putc ('.', fp);
197*946379e7Schristos 	  if (*s != '\0' && *s != ' ')
198*946379e7Schristos 	    putc (' ', fp);
199*946379e7Schristos 	  fputs (s, fp);
200*946379e7Schristos 	  putc ('\n', fp);
201*946379e7Schristos 	}
202*946379e7Schristos     }
203*946379e7Schristos }
204*946379e7Schristos 
205*946379e7Schristos 
206*946379e7Schristos /* Output mp->filepos as a set of comment lines.  */
207*946379e7Schristos 
208*946379e7Schristos void
message_print_comment_filepos(const message_ty * mp,FILE * fp,bool uniforum,size_t page_width)209*946379e7Schristos message_print_comment_filepos (const message_ty *mp, FILE *fp,
210*946379e7Schristos 			       bool uniforum, size_t page_width)
211*946379e7Schristos {
212*946379e7Schristos   if (mp->filepos_count != 0)
213*946379e7Schristos     {
214*946379e7Schristos       if (uniforum)
215*946379e7Schristos 	{
216*946379e7Schristos 	  size_t j;
217*946379e7Schristos 
218*946379e7Schristos 	  for (j = 0; j < mp->filepos_count; ++j)
219*946379e7Schristos 	    {
220*946379e7Schristos 	      lex_pos_ty *pp = &mp->filepos[j];
221*946379e7Schristos 	      char *cp = pp->file_name;
222*946379e7Schristos 	      while (cp[0] == '.' && cp[1] == '/')
223*946379e7Schristos 		cp += 2;
224*946379e7Schristos 	      /* There are two Sun formats to choose from: SunOS and
225*946379e7Schristos 		 Solaris.  Use the Solaris form here.  */
226*946379e7Schristos 	      fprintf (fp, "# File: %s, line: %ld\n",
227*946379e7Schristos 		       cp, (long) pp->line_number);
228*946379e7Schristos 	    }
229*946379e7Schristos 	}
230*946379e7Schristos       else
231*946379e7Schristos 	{
232*946379e7Schristos 	  size_t column;
233*946379e7Schristos 	  size_t j;
234*946379e7Schristos 
235*946379e7Schristos 	  fputs ("#:", fp);
236*946379e7Schristos 	  column = 2;
237*946379e7Schristos 	  for (j = 0; j < mp->filepos_count; ++j)
238*946379e7Schristos 	    {
239*946379e7Schristos 	      lex_pos_ty *pp;
240*946379e7Schristos 	      char buffer[21];
241*946379e7Schristos 	      char *cp;
242*946379e7Schristos 	      size_t len;
243*946379e7Schristos 
244*946379e7Schristos 	      pp = &mp->filepos[j];
245*946379e7Schristos 	      cp = pp->file_name;
246*946379e7Schristos 	      while (cp[0] == '.' && cp[1] == '/')
247*946379e7Schristos 		cp += 2;
248*946379e7Schristos 	      /* Some xgettext input formats, like RST, lack line numbers.  */
249*946379e7Schristos 	      if (pp->line_number == (size_t)(-1))
250*946379e7Schristos 		buffer[0] = '\0';
251*946379e7Schristos 	      else
252*946379e7Schristos 		sprintf (buffer, ":%ld", (long) pp->line_number);
253*946379e7Schristos 	      len = strlen (cp) + strlen (buffer) + 1;
254*946379e7Schristos 	      if (column > 2 && column + len >= page_width)
255*946379e7Schristos 		{
256*946379e7Schristos 		  fputs ("\n#:", fp);
257*946379e7Schristos 		  column = 2;
258*946379e7Schristos 		}
259*946379e7Schristos 	      fprintf (fp, " %s%s", cp, buffer);
260*946379e7Schristos 	      column += len;
261*946379e7Schristos 	    }
262*946379e7Schristos 	  putc ('\n', fp);
263*946379e7Schristos 	}
264*946379e7Schristos     }
265*946379e7Schristos }
266*946379e7Schristos 
267*946379e7Schristos 
268*946379e7Schristos /* Output mp->is_fuzzy, mp->is_format, mp->do_wrap as a comment line.  */
269*946379e7Schristos 
270*946379e7Schristos void
message_print_comment_flags(const message_ty * mp,FILE * fp,bool debug)271*946379e7Schristos message_print_comment_flags (const message_ty *mp, FILE *fp, bool debug)
272*946379e7Schristos {
273*946379e7Schristos   if ((mp->is_fuzzy && mp->msgstr[0] != '\0')
274*946379e7Schristos       || has_significant_format_p (mp->is_format)
275*946379e7Schristos       || mp->do_wrap == no)
276*946379e7Schristos     {
277*946379e7Schristos       bool first_flag = true;
278*946379e7Schristos       size_t i;
279*946379e7Schristos 
280*946379e7Schristos       putc ('#', fp);
281*946379e7Schristos       putc (',', fp);
282*946379e7Schristos 
283*946379e7Schristos       /* We don't print the fuzzy flag if the msgstr is empty.  This
284*946379e7Schristos 	 might be introduced by the user but we want to normalize the
285*946379e7Schristos 	 output.  */
286*946379e7Schristos       if (mp->is_fuzzy && mp->msgstr[0] != '\0')
287*946379e7Schristos 	{
288*946379e7Schristos 	  fputs (" fuzzy", fp);
289*946379e7Schristos 	  first_flag = false;
290*946379e7Schristos 	}
291*946379e7Schristos 
292*946379e7Schristos       for (i = 0; i < NFORMATS; i++)
293*946379e7Schristos 	if (significant_format_p (mp->is_format[i]))
294*946379e7Schristos 	  {
295*946379e7Schristos 	    if (!first_flag)
296*946379e7Schristos 	      putc (',', fp);
297*946379e7Schristos 
298*946379e7Schristos 	    fputs (make_format_description_string (mp->is_format[i],
299*946379e7Schristos 						   format_language[i], debug),
300*946379e7Schristos 		   fp);
301*946379e7Schristos 	    first_flag = false;
302*946379e7Schristos 	  }
303*946379e7Schristos 
304*946379e7Schristos       if (mp->do_wrap == no)
305*946379e7Schristos 	{
306*946379e7Schristos 	  if (!first_flag)
307*946379e7Schristos 	    putc (',', fp);
308*946379e7Schristos 
309*946379e7Schristos 	  fputs (make_c_width_description_string (mp->do_wrap), fp);
310*946379e7Schristos 	  first_flag = false;
311*946379e7Schristos 	}
312*946379e7Schristos 
313*946379e7Schristos       putc ('\n', fp);
314*946379e7Schristos     }
315*946379e7Schristos }
316*946379e7Schristos 
317*946379e7Schristos 
318*946379e7Schristos /* ========= Some parameters for use by 'msgdomain_list_print_po'. ========= */
319*946379e7Schristos 
320*946379e7Schristos 
321*946379e7Schristos /* This variable controls the extent to which the page width applies.
322*946379e7Schristos    True means it applies to message strings and file reference lines.
323*946379e7Schristos    False means it applies to file reference lines only.  */
324*946379e7Schristos static bool wrap_strings = true;
325*946379e7Schristos 
326*946379e7Schristos void
message_page_width_ignore()327*946379e7Schristos message_page_width_ignore ()
328*946379e7Schristos {
329*946379e7Schristos   wrap_strings = false;
330*946379e7Schristos }
331*946379e7Schristos 
332*946379e7Schristos 
333*946379e7Schristos /* These three variables control the output style of the message_print
334*946379e7Schristos    function.  Interface functions for them are to be used.  */
335*946379e7Schristos static bool indent = false;
336*946379e7Schristos static bool uniforum = false;
337*946379e7Schristos static bool escape = false;
338*946379e7Schristos 
339*946379e7Schristos void
message_print_style_indent()340*946379e7Schristos message_print_style_indent ()
341*946379e7Schristos {
342*946379e7Schristos   indent = true;
343*946379e7Schristos }
344*946379e7Schristos 
345*946379e7Schristos void
message_print_style_uniforum()346*946379e7Schristos message_print_style_uniforum ()
347*946379e7Schristos {
348*946379e7Schristos   uniforum = true;
349*946379e7Schristos }
350*946379e7Schristos 
351*946379e7Schristos void
message_print_style_escape(bool flag)352*946379e7Schristos message_print_style_escape (bool flag)
353*946379e7Schristos {
354*946379e7Schristos   escape = flag;
355*946379e7Schristos }
356*946379e7Schristos 
357*946379e7Schristos 
358*946379e7Schristos /* =============== msgdomain_list_print_po() and subroutines. =============== */
359*946379e7Schristos 
360*946379e7Schristos 
361*946379e7Schristos /* A version of memcpy optimized for the case n <= 1.  */
362*946379e7Schristos static inline void
memcpy_small(void * dst,const void * src,size_t n)363*946379e7Schristos memcpy_small (void *dst, const void *src, size_t n)
364*946379e7Schristos {
365*946379e7Schristos   if (n > 0)
366*946379e7Schristos     {
367*946379e7Schristos       char *q = (char *) dst;
368*946379e7Schristos       const char *p = (const char *) src;
369*946379e7Schristos 
370*946379e7Schristos       *q = *p;
371*946379e7Schristos       if (--n > 0)
372*946379e7Schristos 	do *++q = *++p; while (--n > 0);
373*946379e7Schristos     }
374*946379e7Schristos }
375*946379e7Schristos 
376*946379e7Schristos 
377*946379e7Schristos static void
wrap(const message_ty * mp,FILE * fp,const char * line_prefix,int extra_indent,const char * name,const char * value,enum is_wrap do_wrap,size_t page_width,const char * charset)378*946379e7Schristos wrap (const message_ty *mp, FILE *fp, const char *line_prefix, int extra_indent,
379*946379e7Schristos       const char *name, const char *value,
380*946379e7Schristos       enum is_wrap do_wrap, size_t page_width,
381*946379e7Schristos       const char *charset)
382*946379e7Schristos {
383*946379e7Schristos   const char *canon_charset;
384*946379e7Schristos   const char *s;
385*946379e7Schristos   bool first_line;
386*946379e7Schristos #if HAVE_ICONV
387*946379e7Schristos   const char *envval;
388*946379e7Schristos   iconv_t conv;
389*946379e7Schristos #endif
390*946379e7Schristos   bool weird_cjk;
391*946379e7Schristos 
392*946379e7Schristos   canon_charset = po_charset_canonicalize (charset);
393*946379e7Schristos 
394*946379e7Schristos #if HAVE_ICONV
395*946379e7Schristos   /* The old Solaris/openwin msgfmt and GNU msgfmt <= 0.10.35 don't know
396*946379e7Schristos      about multibyte encodings, and require a spurious backslash after
397*946379e7Schristos      every multibyte character whose last byte is 0x5C.  Some programs,
398*946379e7Schristos      like vim, distribute PO files in this broken format.  It is important
399*946379e7Schristos      for such programs that GNU msgmerge continues to support this old
400*946379e7Schristos      PO file format when the Makefile requests it.  */
401*946379e7Schristos   envval = getenv ("OLD_PO_FILE_OUTPUT");
402*946379e7Schristos   if (envval != NULL && *envval != '\0')
403*946379e7Schristos     /* Write a PO file in old format, with extraneous backslashes.  */
404*946379e7Schristos     conv = (iconv_t)(-1);
405*946379e7Schristos   else
406*946379e7Schristos     if (canon_charset == NULL)
407*946379e7Schristos       /* Invalid PO file encoding.  */
408*946379e7Schristos       conv = (iconv_t)(-1);
409*946379e7Schristos     else
410*946379e7Schristos       /* Avoid glibc-2.1 bug with EUC-KR.  */
411*946379e7Schristos # if (__GLIBC__ - 0 == 2 && __GLIBC_MINOR__ - 0 <= 1) && !defined _LIBICONV_VERSION
412*946379e7Schristos       if (strcmp (canon_charset, "EUC-KR") == 0)
413*946379e7Schristos 	conv = (iconv_t)(-1);
414*946379e7Schristos       else
415*946379e7Schristos # endif
416*946379e7Schristos       /* Avoid Solaris 2.9 bug with GB2312, EUC-TW, BIG5, BIG5-HKSCS, GBK,
417*946379e7Schristos 	 GB18030.  */
418*946379e7Schristos # if defined __sun && !defined _LIBICONV_VERSION
419*946379e7Schristos       if (   strcmp (canon_charset, "GB2312") == 0
420*946379e7Schristos 	  || strcmp (canon_charset, "EUC-TW") == 0
421*946379e7Schristos 	  || strcmp (canon_charset, "BIG5") == 0
422*946379e7Schristos 	  || strcmp (canon_charset, "BIG5-HKSCS") == 0
423*946379e7Schristos 	  || strcmp (canon_charset, "GBK") == 0
424*946379e7Schristos 	  || strcmp (canon_charset, "GB18030") == 0)
425*946379e7Schristos 	conv = (iconv_t)(-1);
426*946379e7Schristos       else
427*946379e7Schristos # endif
428*946379e7Schristos       /* Use iconv() to parse multibyte characters.  */
429*946379e7Schristos       conv = iconv_open ("UTF-8", canon_charset);
430*946379e7Schristos 
431*946379e7Schristos   if (conv != (iconv_t)(-1))
432*946379e7Schristos     weird_cjk = false;
433*946379e7Schristos   else
434*946379e7Schristos #endif
435*946379e7Schristos     if (canon_charset == NULL)
436*946379e7Schristos       weird_cjk = false;
437*946379e7Schristos     else
438*946379e7Schristos       weird_cjk = po_is_charset_weird_cjk (canon_charset);
439*946379e7Schristos 
440*946379e7Schristos   if (canon_charset == NULL)
441*946379e7Schristos     canon_charset = po_charset_ascii;
442*946379e7Schristos 
443*946379e7Schristos   /* Loop over the '\n' delimited portions of value.  */
444*946379e7Schristos   s = value;
445*946379e7Schristos   first_line = true;
446*946379e7Schristos   do
447*946379e7Schristos     {
448*946379e7Schristos       /* The usual escapes, as defined by the ANSI C Standard.  */
449*946379e7Schristos #     define is_escape(c) \
450*946379e7Schristos         ((c) == '\a' || (c) == '\b' || (c) == '\f' || (c) == '\n' \
451*946379e7Schristos          || (c) == '\r' || (c) == '\t' || (c) == '\v')
452*946379e7Schristos 
453*946379e7Schristos       const char *es;
454*946379e7Schristos       const char *ep;
455*946379e7Schristos       size_t portion_len;
456*946379e7Schristos       char *portion;
457*946379e7Schristos       char *overrides;
458*946379e7Schristos       char *linebreaks;
459*946379e7Schristos       char *pp;
460*946379e7Schristos       char *op;
461*946379e7Schristos       int startcol, startcol_after_break, width;
462*946379e7Schristos       size_t i;
463*946379e7Schristos 
464*946379e7Schristos       for (es = s; *es != '\0'; )
465*946379e7Schristos 	if (*es++ == '\n')
466*946379e7Schristos 	  break;
467*946379e7Schristos 
468*946379e7Schristos       /* Expand escape sequences in each portion.  */
469*946379e7Schristos       for (ep = s, portion_len = 0; ep < es; ep++)
470*946379e7Schristos 	{
471*946379e7Schristos 	  char c = *ep;
472*946379e7Schristos 	  if (is_escape (c))
473*946379e7Schristos 	    portion_len += 2;
474*946379e7Schristos 	  else if (escape && !c_isprint ((unsigned char) c))
475*946379e7Schristos 	    portion_len += 4;
476*946379e7Schristos 	  else if (c == '\\' || c == '"')
477*946379e7Schristos 	    portion_len += 2;
478*946379e7Schristos 	  else
479*946379e7Schristos 	    {
480*946379e7Schristos #if HAVE_ICONV
481*946379e7Schristos 	      if (conv != (iconv_t)(-1))
482*946379e7Schristos 		{
483*946379e7Schristos 		  /* Skip over a complete multi-byte character.  Don't
484*946379e7Schristos 		     interpret the second byte of a multi-byte character as
485*946379e7Schristos 		     ASCII.  This is needed for the BIG5, BIG5-HKSCS, GBK,
486*946379e7Schristos 		     GB18030, SHIFT_JIS, JOHAB encodings.  */
487*946379e7Schristos 		  char scratchbuf[64];
488*946379e7Schristos 		  const char *inptr = ep;
489*946379e7Schristos 		  size_t insize;
490*946379e7Schristos 		  char *outptr = &scratchbuf[0];
491*946379e7Schristos 		  size_t outsize = sizeof (scratchbuf);
492*946379e7Schristos 		  size_t res;
493*946379e7Schristos 
494*946379e7Schristos 		  res = (size_t)(-1);
495*946379e7Schristos 		  for (insize = 1; inptr + insize <= es; insize++)
496*946379e7Schristos 		    {
497*946379e7Schristos 		      res = iconv (conv,
498*946379e7Schristos 				   (ICONV_CONST char **) &inptr, &insize,
499*946379e7Schristos 				   &outptr, &outsize);
500*946379e7Schristos 		      if (!(res == (size_t)(-1) && errno == EINVAL))
501*946379e7Schristos 			break;
502*946379e7Schristos 		      /* We expect that no input bytes have been consumed
503*946379e7Schristos 			 so far.  */
504*946379e7Schristos 		      if (inptr != ep)
505*946379e7Schristos 			abort ();
506*946379e7Schristos 		    }
507*946379e7Schristos 		  if (res == (size_t)(-1))
508*946379e7Schristos 		    {
509*946379e7Schristos 		      if (errno == EILSEQ)
510*946379e7Schristos 			{
511*946379e7Schristos 			  po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
512*946379e7Schristos 				     _("invalid multibyte sequence"));
513*946379e7Schristos 			  continue;
514*946379e7Schristos 			}
515*946379e7Schristos 		      else
516*946379e7Schristos 			abort ();
517*946379e7Schristos 		    }
518*946379e7Schristos 		  insize = inptr - ep;
519*946379e7Schristos 		  portion_len += insize;
520*946379e7Schristos 		  ep += insize - 1;
521*946379e7Schristos 		}
522*946379e7Schristos 	      else
523*946379e7Schristos #endif
524*946379e7Schristos 		{
525*946379e7Schristos 		  if (weird_cjk
526*946379e7Schristos 		      /* Special handling of encodings with CJK structure.  */
527*946379e7Schristos 		      && ep + 2 <= es
528*946379e7Schristos 		      && (unsigned char) ep[0] >= 0x80
529*946379e7Schristos 		      && (unsigned char) ep[1] >= 0x30)
530*946379e7Schristos 		    {
531*946379e7Schristos 		      portion_len += 2;
532*946379e7Schristos 		      ep += 1;
533*946379e7Schristos 		    }
534*946379e7Schristos 		  else
535*946379e7Schristos 		    portion_len += 1;
536*946379e7Schristos 		}
537*946379e7Schristos 	    }
538*946379e7Schristos 	}
539*946379e7Schristos       portion = (char *) xmalloc (portion_len);
540*946379e7Schristos       overrides = (char *) xmalloc (portion_len);
541*946379e7Schristos       memset (overrides, UC_BREAK_UNDEFINED, portion_len);
542*946379e7Schristos       for (ep = s, pp = portion, op = overrides; ep < es; ep++)
543*946379e7Schristos 	{
544*946379e7Schristos 	  char c = *ep;
545*946379e7Schristos 	  if (is_escape (c))
546*946379e7Schristos 	    {
547*946379e7Schristos 	      switch (c)
548*946379e7Schristos 		{
549*946379e7Schristos 		case '\a': c = 'a'; break;
550*946379e7Schristos 		case '\b': c = 'b'; break;
551*946379e7Schristos 		case '\f': c = 'f'; break;
552*946379e7Schristos 		case '\n': c = 'n'; break;
553*946379e7Schristos 		case '\r': c = 'r'; break;
554*946379e7Schristos 		case '\t': c = 't'; break;
555*946379e7Schristos 		case '\v': c = 'v'; break;
556*946379e7Schristos 		default: abort ();
557*946379e7Schristos 		}
558*946379e7Schristos 	      *pp++ = '\\';
559*946379e7Schristos 	      *pp++ = c;
560*946379e7Schristos 	      op++;
561*946379e7Schristos 	      *op++ = UC_BREAK_PROHIBITED;
562*946379e7Schristos 	      /* We warn about any use of escape sequences beside
563*946379e7Schristos 		 '\n' and '\t'.  */
564*946379e7Schristos 	      if (c != 'n' && c != 't')
565*946379e7Schristos 		{
566*946379e7Schristos 		  char *error_message =
567*946379e7Schristos 		    xasprintf (_("\
568*946379e7Schristos internationalized messages should not contain the `\\%c' escape sequence"),
569*946379e7Schristos 			       c);
570*946379e7Schristos 		  po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0, false,
571*946379e7Schristos 			     error_message);
572*946379e7Schristos 		  free (error_message);
573*946379e7Schristos 		}
574*946379e7Schristos 	    }
575*946379e7Schristos 	  else if (escape && !c_isprint ((unsigned char) c))
576*946379e7Schristos 	    {
577*946379e7Schristos 	      *pp++ = '\\';
578*946379e7Schristos 	      *pp++ = '0' + (((unsigned char) c >> 6) & 7);
579*946379e7Schristos 	      *pp++ = '0' + (((unsigned char) c >> 3) & 7);
580*946379e7Schristos 	      *pp++ = '0' + ((unsigned char) c & 7);
581*946379e7Schristos 	      op++;
582*946379e7Schristos 	      *op++ = UC_BREAK_PROHIBITED;
583*946379e7Schristos 	      *op++ = UC_BREAK_PROHIBITED;
584*946379e7Schristos 	      *op++ = UC_BREAK_PROHIBITED;
585*946379e7Schristos 	    }
586*946379e7Schristos 	  else if (c == '\\' || c == '"')
587*946379e7Schristos 	    {
588*946379e7Schristos 	      *pp++ = '\\';
589*946379e7Schristos 	      *pp++ = c;
590*946379e7Schristos 	      op++;
591*946379e7Schristos 	      *op++ = UC_BREAK_PROHIBITED;
592*946379e7Schristos 	    }
593*946379e7Schristos 	  else
594*946379e7Schristos 	    {
595*946379e7Schristos #if HAVE_ICONV
596*946379e7Schristos 	      if (conv != (iconv_t)(-1))
597*946379e7Schristos 		{
598*946379e7Schristos 		  /* Copy a complete multi-byte character.  Don't
599*946379e7Schristos 		     interpret the second byte of a multi-byte character as
600*946379e7Schristos 		     ASCII.  This is needed for the BIG5, BIG5-HKSCS, GBK,
601*946379e7Schristos 		     GB18030, SHIFT_JIS, JOHAB encodings.  */
602*946379e7Schristos 		  char scratchbuf[64];
603*946379e7Schristos 		  const char *inptr = ep;
604*946379e7Schristos 		  size_t insize;
605*946379e7Schristos 		  char *outptr = &scratchbuf[0];
606*946379e7Schristos 		  size_t outsize = sizeof (scratchbuf);
607*946379e7Schristos 		  size_t res;
608*946379e7Schristos 
609*946379e7Schristos 		  res = (size_t)(-1);
610*946379e7Schristos 		  for (insize = 1; inptr + insize <= es; insize++)
611*946379e7Schristos 		    {
612*946379e7Schristos 		      res = iconv (conv,
613*946379e7Schristos 				   (ICONV_CONST char **) &inptr, &insize,
614*946379e7Schristos 				   &outptr, &outsize);
615*946379e7Schristos 		      if (!(res == (size_t)(-1) && errno == EINVAL))
616*946379e7Schristos 			break;
617*946379e7Schristos 		      /* We expect that no input bytes have been consumed
618*946379e7Schristos 			 so far.  */
619*946379e7Schristos 		      if (inptr != ep)
620*946379e7Schristos 			abort ();
621*946379e7Schristos 		    }
622*946379e7Schristos 		  if (res == (size_t)(-1))
623*946379e7Schristos 		    {
624*946379e7Schristos 		      if (errno == EILSEQ)
625*946379e7Schristos 			{
626*946379e7Schristos 			  po_xerror (PO_SEVERITY_ERROR, mp, NULL, 0, 0,
627*946379e7Schristos 				     false, _("invalid multibyte sequence"));
628*946379e7Schristos 			  continue;
629*946379e7Schristos 			}
630*946379e7Schristos 		      else
631*946379e7Schristos 			abort ();
632*946379e7Schristos 		    }
633*946379e7Schristos 		  insize = inptr - ep;
634*946379e7Schristos 		  memcpy_small (pp, ep, insize);
635*946379e7Schristos 		  pp += insize;
636*946379e7Schristos 		  op += insize;
637*946379e7Schristos 		  ep += insize - 1;
638*946379e7Schristos 		}
639*946379e7Schristos 	      else
640*946379e7Schristos #endif
641*946379e7Schristos 		{
642*946379e7Schristos 		  if (weird_cjk
643*946379e7Schristos 		      /* Special handling of encodings with CJK structure.  */
644*946379e7Schristos 		      && ep + 2 <= es
645*946379e7Schristos 		      && (unsigned char) c >= 0x80
646*946379e7Schristos 		      && (unsigned char) ep[1] >= 0x30)
647*946379e7Schristos 		    {
648*946379e7Schristos 		      *pp++ = c;
649*946379e7Schristos 		      ep += 1;
650*946379e7Schristos 		      *pp++ = *ep;
651*946379e7Schristos 		      op += 2;
652*946379e7Schristos 		    }
653*946379e7Schristos 		  else
654*946379e7Schristos 		    {
655*946379e7Schristos 		      *pp++ = c;
656*946379e7Schristos 		      op++;
657*946379e7Schristos 		    }
658*946379e7Schristos 		}
659*946379e7Schristos 	    }
660*946379e7Schristos 	}
661*946379e7Schristos 
662*946379e7Schristos       /* Don't break immediately before the "\n" at the end.  */
663*946379e7Schristos       if (es > s && es[-1] == '\n')
664*946379e7Schristos 	overrides[portion_len - 2] = UC_BREAK_PROHIBITED;
665*946379e7Schristos 
666*946379e7Schristos       linebreaks = (char *) xmalloc (portion_len);
667*946379e7Schristos 
668*946379e7Schristos       /* Subsequent lines after a break are all indented.
669*946379e7Schristos 	 See INDENT-S.  */
670*946379e7Schristos       startcol_after_break = (line_prefix ? strlen (line_prefix) : 0);
671*946379e7Schristos       if (indent)
672*946379e7Schristos 	startcol_after_break = (startcol_after_break + extra_indent + 8) & ~7;
673*946379e7Schristos       startcol_after_break++;
674*946379e7Schristos 
675*946379e7Schristos       /* The line width.  Allow room for the closing quote character.  */
676*946379e7Schristos       width = (wrap_strings && do_wrap != no ? page_width : INT_MAX) - 1;
677*946379e7Schristos       /* Adjust for indentation of subsequent lines.  */
678*946379e7Schristos       width -= startcol_after_break;
679*946379e7Schristos 
680*946379e7Schristos     recompute:
681*946379e7Schristos       /* The line starts with different things depending on whether it
682*946379e7Schristos 	 is the first line, and if we are using the indented style.
683*946379e7Schristos 	 See INDENT-F.  */
684*946379e7Schristos       startcol = (line_prefix ? strlen (line_prefix) : 0);
685*946379e7Schristos       if (first_line)
686*946379e7Schristos 	{
687*946379e7Schristos 	  startcol += strlen (name);
688*946379e7Schristos 	  if (indent)
689*946379e7Schristos 	    startcol = (startcol + extra_indent + 8) & ~7;
690*946379e7Schristos 	  else
691*946379e7Schristos 	    startcol++;
692*946379e7Schristos 	}
693*946379e7Schristos       else
694*946379e7Schristos 	{
695*946379e7Schristos 	  if (indent)
696*946379e7Schristos 	    startcol = (startcol + extra_indent + 8) & ~7;
697*946379e7Schristos 	}
698*946379e7Schristos       /* Allow room for the opening quote character.  */
699*946379e7Schristos       startcol++;
700*946379e7Schristos       /* Adjust for indentation of subsequent lines.  */
701*946379e7Schristos       startcol -= startcol_after_break;
702*946379e7Schristos 
703*946379e7Schristos       /* Do line breaking on the portion.  */
704*946379e7Schristos       mbs_width_linebreaks (portion, portion_len, width, startcol, 0,
705*946379e7Schristos 			    overrides, canon_charset, linebreaks);
706*946379e7Schristos 
707*946379e7Schristos       /* If this is the first line, and we are not using the indented
708*946379e7Schristos 	 style, and the line would wrap, then use an empty first line
709*946379e7Schristos 	 and restart.  */
710*946379e7Schristos       if (first_line && !indent
711*946379e7Schristos 	  && portion_len > 0
712*946379e7Schristos 	  && (*es != '\0'
713*946379e7Schristos 	      || startcol > width
714*946379e7Schristos 	      || memchr (linebreaks, UC_BREAK_POSSIBLE, portion_len) != NULL))
715*946379e7Schristos 	{
716*946379e7Schristos 	  if (line_prefix != NULL)
717*946379e7Schristos 	    fputs (line_prefix, fp);
718*946379e7Schristos 	  fputs (name, fp);
719*946379e7Schristos 	  fputs (" \"\"\n", fp);
720*946379e7Schristos 	  first_line = false;
721*946379e7Schristos 	  /* Recompute startcol and linebreaks.  */
722*946379e7Schristos 	  goto recompute;
723*946379e7Schristos 	}
724*946379e7Schristos 
725*946379e7Schristos       /* Print the beginning of the line.  This will depend on whether
726*946379e7Schristos 	 this is the first line, and if the indented style is being
727*946379e7Schristos 	 used.  INDENT-F.  */
728*946379e7Schristos       if (line_prefix != NULL)
729*946379e7Schristos 	fputs (line_prefix, fp);
730*946379e7Schristos       if (first_line)
731*946379e7Schristos 	{
732*946379e7Schristos 	  fputs (name, fp);
733*946379e7Schristos 	  if (indent)
734*946379e7Schristos 	    {
735*946379e7Schristos 	      if (extra_indent > 0)
736*946379e7Schristos 		fwrite ("        ", 1, extra_indent, fp);
737*946379e7Schristos 	      putc ('\t', fp);
738*946379e7Schristos 	    }
739*946379e7Schristos 	  else
740*946379e7Schristos 	    putc (' ', fp);
741*946379e7Schristos 	  first_line = false;
742*946379e7Schristos 	}
743*946379e7Schristos       else
744*946379e7Schristos 	{
745*946379e7Schristos 	  if (indent)
746*946379e7Schristos 	    {
747*946379e7Schristos 	      if (extra_indent > 0)
748*946379e7Schristos 		fwrite ("        ", 1, extra_indent, fp);
749*946379e7Schristos 	      putc ('\t', fp);
750*946379e7Schristos 	    }
751*946379e7Schristos 	}
752*946379e7Schristos 
753*946379e7Schristos       /* Print the portion itself, with linebreaks where necessary.  */
754*946379e7Schristos       putc ('"', fp);
755*946379e7Schristos       for (i = 0; i < portion_len; i++)
756*946379e7Schristos 	{
757*946379e7Schristos 	  if (linebreaks[i] == UC_BREAK_POSSIBLE)
758*946379e7Schristos 	    {
759*946379e7Schristos 	      fputs ("\"\n", fp);
760*946379e7Schristos 	      /* INDENT-S.  */
761*946379e7Schristos 	      if (line_prefix != NULL)
762*946379e7Schristos 		fputs (line_prefix, fp);
763*946379e7Schristos 	      if (indent)
764*946379e7Schristos 		putc ('\t', fp);
765*946379e7Schristos 	      putc ('"', fp);
766*946379e7Schristos 	    }
767*946379e7Schristos 	  putc (portion[i], fp);
768*946379e7Schristos 	}
769*946379e7Schristos       fputs ("\"\n", fp);
770*946379e7Schristos 
771*946379e7Schristos       free (linebreaks);
772*946379e7Schristos       free (overrides);
773*946379e7Schristos       free (portion);
774*946379e7Schristos 
775*946379e7Schristos       s = es;
776*946379e7Schristos #     undef is_escape
777*946379e7Schristos     }
778*946379e7Schristos   while (*s);
779*946379e7Schristos 
780*946379e7Schristos #if HAVE_ICONV
781*946379e7Schristos   if (conv != (iconv_t)(-1))
782*946379e7Schristos     iconv_close (conv);
783*946379e7Schristos #endif
784*946379e7Schristos }
785*946379e7Schristos 
786*946379e7Schristos 
787*946379e7Schristos static void
print_blank_line(FILE * fp)788*946379e7Schristos print_blank_line (FILE *fp)
789*946379e7Schristos {
790*946379e7Schristos   if (uniforum)
791*946379e7Schristos     fputs ("#\n", fp);
792*946379e7Schristos   else
793*946379e7Schristos     putc ('\n', fp);
794*946379e7Schristos }
795*946379e7Schristos 
796*946379e7Schristos 
797*946379e7Schristos static void
message_print(const message_ty * mp,FILE * fp,const char * charset,size_t page_width,bool blank_line,bool debug)798*946379e7Schristos message_print (const message_ty *mp, FILE *fp, const char *charset,
799*946379e7Schristos 	       size_t page_width, bool blank_line, bool debug)
800*946379e7Schristos {
801*946379e7Schristos   int extra_indent;
802*946379e7Schristos 
803*946379e7Schristos   /* Separate messages with a blank line.  Uniforum doesn't like blank
804*946379e7Schristos      lines, so use an empty comment (unless there already is one).  */
805*946379e7Schristos   if (blank_line && (!uniforum
806*946379e7Schristos 		     || mp->comment == NULL
807*946379e7Schristos 		     || mp->comment->nitems == 0
808*946379e7Schristos 		     || mp->comment->item[0][0] != '\0'))
809*946379e7Schristos     print_blank_line (fp);
810*946379e7Schristos 
811*946379e7Schristos   /* Print translator comment if available.  */
812*946379e7Schristos   message_print_comment (mp, fp);
813*946379e7Schristos 
814*946379e7Schristos   /* Print xgettext extracted comments.  */
815*946379e7Schristos   message_print_comment_dot (mp, fp);
816*946379e7Schristos 
817*946379e7Schristos   /* Print the file position comments.  This will help a human who is
818*946379e7Schristos      trying to navigate the sources.  There is no problem of getting
819*946379e7Schristos      repeated positions, because duplicates are checked for.  */
820*946379e7Schristos   message_print_comment_filepos (mp, fp, uniforum, page_width);
821*946379e7Schristos 
822*946379e7Schristos   /* Print flag information in special comment.  */
823*946379e7Schristos   message_print_comment_flags (mp, fp, debug);
824*946379e7Schristos 
825*946379e7Schristos   /* Print the previous msgid.  This helps the translator when the msgid has
826*946379e7Schristos      only slightly changed.  */
827*946379e7Schristos   if (mp->prev_msgctxt != NULL)
828*946379e7Schristos     wrap (mp, fp, "#| ", 0, "msgctxt", mp->prev_msgctxt, mp->do_wrap,
829*946379e7Schristos 	  page_width, charset);
830*946379e7Schristos   if (mp->prev_msgid != NULL)
831*946379e7Schristos     wrap (mp, fp, "#| ", 0, "msgid", mp->prev_msgid, mp->do_wrap, page_width,
832*946379e7Schristos 	  charset);
833*946379e7Schristos   if (mp->prev_msgid_plural != NULL)
834*946379e7Schristos     wrap (mp, fp, "#| ", 0, "msgid_plural", mp->prev_msgid_plural, mp->do_wrap,
835*946379e7Schristos 	  page_width, charset);
836*946379e7Schristos   extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL
837*946379e7Schristos 		  || mp->prev_msgid_plural != NULL
838*946379e7Schristos 		  ? 3
839*946379e7Schristos 		  : 0);
840*946379e7Schristos 
841*946379e7Schristos   /* Print each of the message components.  Wrap them nicely so they
842*946379e7Schristos      are as readable as possible.  If there is no recorded msgstr for
843*946379e7Schristos      this domain, emit an empty string.  */
844*946379e7Schristos   if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt)
845*946379e7Schristos       && po_charset_canonicalize (charset) != po_charset_utf8)
846*946379e7Schristos     {
847*946379e7Schristos       char *warning_message =
848*946379e7Schristos 	xasprintf (_("\
849*946379e7Schristos The following msgctxt contains non-ASCII characters.\n\
850*946379e7Schristos This will cause problems to translators who use a character encoding\n\
851*946379e7Schristos different from yours. Consider using a pure ASCII msgctxt instead.\n\
852*946379e7Schristos %s\n"), mp->msgctxt);
853*946379e7Schristos       po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
854*946379e7Schristos       free (warning_message);
855*946379e7Schristos     }
856*946379e7Schristos   if (!is_ascii_string (mp->msgid)
857*946379e7Schristos       && po_charset_canonicalize (charset) != po_charset_utf8)
858*946379e7Schristos     {
859*946379e7Schristos       char *warning_message =
860*946379e7Schristos 	xasprintf (_("\
861*946379e7Schristos The following msgid contains non-ASCII characters.\n\
862*946379e7Schristos This will cause problems to translators who use a character encoding\n\
863*946379e7Schristos different from yours. Consider using a pure ASCII msgid instead.\n\
864*946379e7Schristos %s\n"), mp->msgid);
865*946379e7Schristos       po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
866*946379e7Schristos       free (warning_message);
867*946379e7Schristos     }
868*946379e7Schristos   if (mp->msgctxt != NULL)
869*946379e7Schristos     wrap (mp, fp, NULL, extra_indent, "msgctxt", mp->msgctxt, mp->do_wrap,
870*946379e7Schristos 	  page_width, charset);
871*946379e7Schristos   wrap (mp, fp, NULL, extra_indent, "msgid", mp->msgid, mp->do_wrap,
872*946379e7Schristos 	  page_width, charset);
873*946379e7Schristos   if (mp->msgid_plural != NULL)
874*946379e7Schristos     wrap (mp, fp, NULL, extra_indent, "msgid_plural", mp->msgid_plural,
875*946379e7Schristos 	  mp->do_wrap, page_width, charset);
876*946379e7Schristos 
877*946379e7Schristos   if (mp->msgid_plural == NULL)
878*946379e7Schristos     wrap (mp, fp, NULL, extra_indent, "msgstr", mp->msgstr, mp->do_wrap,
879*946379e7Schristos 	  page_width, charset);
880*946379e7Schristos   else
881*946379e7Schristos     {
882*946379e7Schristos       char prefix_buf[20];
883*946379e7Schristos       unsigned int i;
884*946379e7Schristos       const char *p;
885*946379e7Schristos 
886*946379e7Schristos       for (p = mp->msgstr, i = 0;
887*946379e7Schristos 	   p < mp->msgstr + mp->msgstr_len;
888*946379e7Schristos 	   p += strlen (p) + 1, i++)
889*946379e7Schristos 	{
890*946379e7Schristos 	  sprintf (prefix_buf, "msgstr[%u]", i);
891*946379e7Schristos 	  wrap (mp, fp, NULL, extra_indent, prefix_buf, p, mp->do_wrap,
892*946379e7Schristos 		page_width, charset);
893*946379e7Schristos 	}
894*946379e7Schristos     }
895*946379e7Schristos }
896*946379e7Schristos 
897*946379e7Schristos 
898*946379e7Schristos static void
message_print_obsolete(const message_ty * mp,FILE * fp,const char * charset,size_t page_width,bool blank_line)899*946379e7Schristos message_print_obsolete (const message_ty *mp, FILE *fp, const char *charset,
900*946379e7Schristos 			size_t page_width, bool blank_line)
901*946379e7Schristos {
902*946379e7Schristos   int extra_indent;
903*946379e7Schristos 
904*946379e7Schristos   /* If msgstr is the empty string we print nothing.  */
905*946379e7Schristos   if (mp->msgstr[0] == '\0')
906*946379e7Schristos     return;
907*946379e7Schristos 
908*946379e7Schristos   /* Separate messages with a blank line.  Uniforum doesn't like blank
909*946379e7Schristos      lines, so use an empty comment (unless there already is one).  */
910*946379e7Schristos   if (blank_line)
911*946379e7Schristos     print_blank_line (fp);
912*946379e7Schristos 
913*946379e7Schristos   /* Print translator comment if available.  */
914*946379e7Schristos   message_print_comment (mp, fp);
915*946379e7Schristos 
916*946379e7Schristos   /* Print xgettext extracted comments (normally empty).  */
917*946379e7Schristos   message_print_comment_dot (mp, fp);
918*946379e7Schristos 
919*946379e7Schristos   /* Print the file position comments (normally empty).  */
920*946379e7Schristos   message_print_comment_filepos (mp, fp, uniforum, page_width);
921*946379e7Schristos 
922*946379e7Schristos   /* Print flag information in special comment.  */
923*946379e7Schristos   if (mp->is_fuzzy)
924*946379e7Schristos     {
925*946379e7Schristos       bool first = true;
926*946379e7Schristos 
927*946379e7Schristos       putc ('#', fp);
928*946379e7Schristos       putc (',', fp);
929*946379e7Schristos 
930*946379e7Schristos       if (mp->is_fuzzy)
931*946379e7Schristos 	{
932*946379e7Schristos 	  fputs (" fuzzy", fp);
933*946379e7Schristos 	  first = false;
934*946379e7Schristos 	}
935*946379e7Schristos 
936*946379e7Schristos       putc ('\n', fp);
937*946379e7Schristos     }
938*946379e7Schristos 
939*946379e7Schristos   /* Print the previous msgid.  This helps the translator when the msgid has
940*946379e7Schristos      only slightly changed.  */
941*946379e7Schristos   if (mp->prev_msgctxt != NULL)
942*946379e7Schristos     wrap (mp, fp, "#~| ", 0, "msgctxt", mp->prev_msgctxt, mp->do_wrap,
943*946379e7Schristos 	  page_width, charset);
944*946379e7Schristos   if (mp->prev_msgid != NULL)
945*946379e7Schristos     wrap (mp, fp, "#~| ", 0, "msgid", mp->prev_msgid, mp->do_wrap, page_width,
946*946379e7Schristos 	  charset);
947*946379e7Schristos   if (mp->prev_msgid_plural != NULL)
948*946379e7Schristos     wrap (mp, fp, "#~| ", 0, "msgid_plural", mp->prev_msgid_plural, mp->do_wrap,
949*946379e7Schristos 	  page_width, charset);
950*946379e7Schristos   extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL
951*946379e7Schristos 		  || mp->prev_msgid_plural != NULL
952*946379e7Schristos 		  ? 1
953*946379e7Schristos 		  : 0);
954*946379e7Schristos 
955*946379e7Schristos   /* Print each of the message components.  Wrap them nicely so they
956*946379e7Schristos      are as readable as possible.  */
957*946379e7Schristos   if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt)
958*946379e7Schristos       && po_charset_canonicalize (charset) != po_charset_utf8)
959*946379e7Schristos     {
960*946379e7Schristos       char *warning_message =
961*946379e7Schristos 	xasprintf (_("\
962*946379e7Schristos The following msgctxt contains non-ASCII characters.\n\
963*946379e7Schristos This will cause problems to translators who use a character encoding\n\
964*946379e7Schristos different from yours. Consider using a pure ASCII msgctxt instead.\n\
965*946379e7Schristos %s\n"), mp->msgctxt);
966*946379e7Schristos       po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
967*946379e7Schristos       free (warning_message);
968*946379e7Schristos     }
969*946379e7Schristos   if (!is_ascii_string (mp->msgid)
970*946379e7Schristos       && po_charset_canonicalize (charset) != po_charset_utf8)
971*946379e7Schristos     {
972*946379e7Schristos       char *warning_message =
973*946379e7Schristos 	xasprintf (_("\
974*946379e7Schristos The following msgid contains non-ASCII characters.\n\
975*946379e7Schristos This will cause problems to translators who use a character encoding\n\
976*946379e7Schristos different from yours. Consider using a pure ASCII msgid instead.\n\
977*946379e7Schristos %s\n"), mp->msgid);
978*946379e7Schristos       po_xerror (PO_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
979*946379e7Schristos       free (warning_message);
980*946379e7Schristos     }
981*946379e7Schristos   if (mp->msgctxt != NULL)
982*946379e7Schristos     wrap (mp, fp, "#~ ", extra_indent, "msgctxt", mp->msgctxt, mp->do_wrap,
983*946379e7Schristos 	  page_width, charset);
984*946379e7Schristos   wrap (mp, fp, "#~ ", extra_indent, "msgid", mp->msgid, mp->do_wrap,
985*946379e7Schristos 	page_width, charset);
986*946379e7Schristos   if (mp->msgid_plural != NULL)
987*946379e7Schristos     wrap (mp, fp, "#~ ", extra_indent, "msgid_plural", mp->msgid_plural,
988*946379e7Schristos 	  mp->do_wrap, page_width, charset);
989*946379e7Schristos 
990*946379e7Schristos   if (mp->msgid_plural == NULL)
991*946379e7Schristos     wrap (mp, fp, "#~ ", extra_indent, "msgstr", mp->msgstr, mp->do_wrap,
992*946379e7Schristos 	  page_width, charset);
993*946379e7Schristos   else
994*946379e7Schristos     {
995*946379e7Schristos       char prefix_buf[20];
996*946379e7Schristos       unsigned int i;
997*946379e7Schristos       const char *p;
998*946379e7Schristos 
999*946379e7Schristos       for (p = mp->msgstr, i = 0;
1000*946379e7Schristos 	   p < mp->msgstr + mp->msgstr_len;
1001*946379e7Schristos 	   p += strlen (p) + 1, i++)
1002*946379e7Schristos 	{
1003*946379e7Schristos 	  sprintf (prefix_buf, "msgstr[%u]", i);
1004*946379e7Schristos 	  wrap (mp, fp, "#~ ", extra_indent, prefix_buf, p, mp->do_wrap,
1005*946379e7Schristos 		page_width, charset);
1006*946379e7Schristos 	}
1007*946379e7Schristos     }
1008*946379e7Schristos }
1009*946379e7Schristos 
1010*946379e7Schristos 
1011*946379e7Schristos static void
msgdomain_list_print_po(msgdomain_list_ty * mdlp,FILE * fp,size_t page_width,bool debug)1012*946379e7Schristos msgdomain_list_print_po (msgdomain_list_ty *mdlp, FILE *fp, size_t page_width,
1013*946379e7Schristos 			 bool debug)
1014*946379e7Schristos {
1015*946379e7Schristos   size_t j, k;
1016*946379e7Schristos   bool blank_line;
1017*946379e7Schristos 
1018*946379e7Schristos   /* Write out the messages for each domain.  */
1019*946379e7Schristos   blank_line = false;
1020*946379e7Schristos   for (k = 0; k < mdlp->nitems; k++)
1021*946379e7Schristos     {
1022*946379e7Schristos       message_list_ty *mlp;
1023*946379e7Schristos       const char *header;
1024*946379e7Schristos       const char *charset;
1025*946379e7Schristos       char *allocated_charset;
1026*946379e7Schristos 
1027*946379e7Schristos       /* If the first domain is the default, don't bother emitting
1028*946379e7Schristos 	 the domain name, because it is the default.  */
1029*946379e7Schristos       if (!(k == 0
1030*946379e7Schristos 	    && strcmp (mdlp->item[k]->domain, MESSAGE_DOMAIN_DEFAULT) == 0))
1031*946379e7Schristos 	{
1032*946379e7Schristos 	  if (blank_line)
1033*946379e7Schristos 	    print_blank_line (fp);
1034*946379e7Schristos 	  fprintf (fp, "domain \"%s\"\n", mdlp->item[k]->domain);
1035*946379e7Schristos 	  blank_line = true;
1036*946379e7Schristos 	}
1037*946379e7Schristos 
1038*946379e7Schristos       mlp = mdlp->item[k]->messages;
1039*946379e7Schristos 
1040*946379e7Schristos       /* Search the header entry.  */
1041*946379e7Schristos       header = NULL;
1042*946379e7Schristos       for (j = 0; j < mlp->nitems; ++j)
1043*946379e7Schristos 	if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1044*946379e7Schristos 	  {
1045*946379e7Schristos 	    header = mlp->item[j]->msgstr;
1046*946379e7Schristos 	    break;
1047*946379e7Schristos 	  }
1048*946379e7Schristos 
1049*946379e7Schristos       /* Extract the charset name.  */
1050*946379e7Schristos       charset = "ASCII";
1051*946379e7Schristos       allocated_charset = NULL;
1052*946379e7Schristos       if (header != NULL)
1053*946379e7Schristos 	{
1054*946379e7Schristos 	  const char *charsetstr = c_strstr (header, "charset=");
1055*946379e7Schristos 
1056*946379e7Schristos 	  if (charsetstr != NULL)
1057*946379e7Schristos 	    {
1058*946379e7Schristos 	      size_t len;
1059*946379e7Schristos 
1060*946379e7Schristos 	      charsetstr += strlen ("charset=");
1061*946379e7Schristos 	      len = strcspn (charsetstr, " \t\n");
1062*946379e7Schristos 	      allocated_charset = (char *) xallocsa (len + 1);
1063*946379e7Schristos 	      memcpy (allocated_charset, charsetstr, len);
1064*946379e7Schristos 	      allocated_charset[len] = '\0';
1065*946379e7Schristos 	      charset = allocated_charset;
1066*946379e7Schristos 
1067*946379e7Schristos 	      /* Treat the dummy default value as if it were absent.  */
1068*946379e7Schristos 	      if (strcmp (charset, "CHARSET") == 0)
1069*946379e7Schristos 		charset = "ASCII";
1070*946379e7Schristos 	    }
1071*946379e7Schristos 	}
1072*946379e7Schristos 
1073*946379e7Schristos       /* Write out each of the messages for this domain.  */
1074*946379e7Schristos       for (j = 0; j < mlp->nitems; ++j)
1075*946379e7Schristos 	if (!mlp->item[j]->obsolete)
1076*946379e7Schristos 	  {
1077*946379e7Schristos 	    message_print (mlp->item[j], fp, charset, page_width, blank_line,
1078*946379e7Schristos 			   debug);
1079*946379e7Schristos 	    blank_line = true;
1080*946379e7Schristos 	  }
1081*946379e7Schristos 
1082*946379e7Schristos       /* Write out each of the obsolete messages for this domain.  */
1083*946379e7Schristos       for (j = 0; j < mlp->nitems; ++j)
1084*946379e7Schristos 	if (mlp->item[j]->obsolete)
1085*946379e7Schristos 	  {
1086*946379e7Schristos 	    message_print_obsolete (mlp->item[j], fp, charset, page_width,
1087*946379e7Schristos 				    blank_line);
1088*946379e7Schristos 	    blank_line = true;
1089*946379e7Schristos 	  }
1090*946379e7Schristos 
1091*946379e7Schristos       if (allocated_charset != NULL)
1092*946379e7Schristos 	freesa (allocated_charset);
1093*946379e7Schristos     }
1094*946379e7Schristos }
1095*946379e7Schristos 
1096*946379e7Schristos 
1097*946379e7Schristos /* Describes a PO file in .po syntax.  */
1098*946379e7Schristos const struct catalog_output_format output_format_po =
1099*946379e7Schristos {
1100*946379e7Schristos   msgdomain_list_print_po,		/* print */
1101*946379e7Schristos   false,				/* requires_utf8 */
1102*946379e7Schristos   true,					/* supports_multiple_domains */
1103*946379e7Schristos   true,					/* supports_contexts */
1104*946379e7Schristos   true,					/* supports_plurals */
1105*946379e7Schristos   false,				/* alternative_is_po */
1106*946379e7Schristos   false					/* alternative_is_java_class */
1107*946379e7Schristos };
1108