Coverage Report

Created: 2026-03-12 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gettext/gettext-tools/src/write-po.c
Line
Count
Source
1
/* GNU gettext - internationalization aids
2
   Copyright (C) 1995-2026 Free Software Foundation, Inc.
3
4
   This program is free software: you can redistribute it and/or modify
5
   it under the terms of the GNU General Public License as published by
6
   the Free Software Foundation; either version 3 of the License, or
7
   (at your option) any later version.
8
9
   This program is distributed in the hope that it will be useful,
10
   but WITHOUT ANY WARRANTY; without even the implied warranty of
11
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
   GNU General Public License for more details.
13
14
   You should have received a copy of the GNU General Public License
15
   along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
16
17
/* Written by Peter Miller, Ulrich Drepper, and Bruno Haible.  */
18
19
#include <config.h>
20
#include <alloca.h>
21
22
/* Specification.  */
23
#include "write-po.h"
24
25
#include <errno.h>
26
#include <limits.h>
27
#include <stdio.h>
28
#include <stdlib.h>
29
#include <string.h>
30
31
#if HAVE_ICONV
32
# include <iconv.h>
33
#endif
34
35
#include <textstyle.h>
36
37
#include "attribute.h"
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 "pos.h"
44
#include "write-catalog.h"
45
#include "xalloc.h"
46
#include "xmalloca.h"
47
#include "c-strstr.h"
48
#include "xvasprintf.h"
49
#include "verify.h"
50
#include "xerror-handler.h"
51
#include "gettext.h"
52
53
/* Our regular abbreviation.  */
54
0
#define _(str) gettext (str)
55
56
57
/* =================== Putting together a #, flags line. =================== */
58
59
60
/* Convert IS_FORMAT in the context of programming language LANG to a flag
61
   string for use in #, flags.  */
62
63
char *
64
make_format_description_string (enum is_format is_format, const char *lang,
65
                                bool debug)
66
0
{
67
0
  char *result;
68
69
0
  switch (is_format)
70
0
    {
71
    /* Cf. possible_format_p.  */
72
0
    case possible:
73
0
      if (debug)
74
0
        {
75
0
          result = xasprintf ("possible-%s-format", lang);
76
0
          break;
77
0
        }
78
0
      FALLTHROUGH;
79
0
    case yes_according_to_context:
80
0
    case yes:
81
0
      result = xasprintf ("%s-format", lang);
82
0
      break;
83
    /* Cf. not_format_p.  */
84
0
    case no:
85
0
      result = xasprintf ("no-%s-format", lang);
86
0
      break;
87
0
    default:
88
      /* The others have already been filtered out by significant_format_p.  */
89
0
      abort ();
90
0
    }
91
92
0
  assume (result != NULL);
93
0
  return result;
94
0
}
95
96
97
/* Return true if IS_FORMAT is worth mentioning in a #, flags list.
98
   This is the same as
99
     possible_format_p (is_format) || not_format_p (is_format).  */
100
101
bool
102
significant_format_p (enum is_format is_format)
103
0
{
104
0
  return is_format != undecided && is_format != impossible;
105
0
}
106
107
108
/* Return true if one of IS_FORMAT is worth mentioning in a #, flags list.  */
109
110
static bool
111
has_significant_format_p (const enum is_format is_format[NFORMATS])
112
0
{
113
0
  for (size_t i = 0; i < NFORMATS; i++)
114
0
    if (significant_format_p (is_format[i]))
115
0
      return true;
116
0
  return false;
117
0
}
118
119
120
/* Convert a RANGE to a freshly allocated string for use in #, flags.  */
121
122
char *
123
make_range_description_string (struct argument_range range)
124
0
{
125
0
  char *result = xasprintf ("range: %d..%d", range.min, range.max);
126
0
  assume (result != NULL);
127
0
  return result;
128
0
}
129
130
131
/* Convert a wrapping flag DO_WRAP to a string for use in #, flags.  */
132
133
static const char *
134
make_c_width_description_string (enum is_wrap do_wrap)
135
0
{
136
0
  const char *result = NULL;
137
138
0
  switch (do_wrap)
139
0
    {
140
0
    case yes:
141
0
      result = "wrap";
142
0
      break;
143
0
    case no:
144
0
      result = "no-wrap";
145
0
      break;
146
0
    default:
147
0
      abort ();
148
0
    }
149
150
0
  return result;
151
0
}
152
153
154
/* ========================== Styling primitives. ========================== */
155
156
157
/* When compiled in src, enable styling support.
158
   When compiled in libgettextpo, don't enable styling support.  */
159
#ifdef GETTEXTDATADIR
160
161
/* All ostream_t instances are in fact styled_ostream_t instances.  */
162
#define is_stylable(stream) true
163
164
/* Start a run of text belonging to a given CSS class.  */
165
static inline void
166
begin_css_class (ostream_t stream, const char *classname)
167
{
168
  styled_ostream_begin_use_class ((styled_ostream_t) stream, classname);
169
}
170
171
/* End a run of text belonging to a given CSS class.  */
172
static inline void
173
end_css_class (ostream_t stream, const char *classname)
174
{
175
  styled_ostream_end_use_class ((styled_ostream_t) stream, classname);
176
}
177
178
#else
179
180
#define is_stylable(stream) false
181
0
#define begin_css_class(stream,classname) (void)(classname)
182
0
#define end_css_class(stream,classname) (void)(classname)
183
184
#endif
185
186
/* CSS classes at message level.  */
187
static const char class_header[] = "header";
188
static const char class_translated[] = "translated";
189
static const char class_untranslated[] = "untranslated";
190
static const char class_fuzzy[] = "fuzzy";
191
static const char class_obsolete[] = "obsolete";
192
193
/* CSS classes describing the parts of a message.  */
194
static const char class_comment[] = "comment";
195
static const char class_translator_comment[] = "translator-comment";
196
static const char class_extracted_comment[] = "extracted-comment";
197
static const char class_reference_comment[] = "reference-comment";
198
static const char class_reference[] = "reference";
199
static const char class_flag_comment[] = "flag-comment";
200
static const char class_flag[] = "flag";
201
static const char class_fuzzy_flag[] = "fuzzy-flag";
202
static const char class_previous_comment[] = "previous-comment";
203
static const char class_previous[] = "previous";
204
static const char class_msgid[] = "msgid";
205
static const char class_msgstr[] = "msgstr";
206
static const char class_keyword[] = "keyword";
207
static const char class_string[] = "string";
208
209
/* CSS classes for the contents of strings.  */
210
static const char class_text[] = "text";
211
static const char class_escape_sequence[] = "escape-sequence";
212
static const char class_format_directive[] = "format-directive";
213
static const char class_invalid_format_directive[] = "invalid-format-directive";
214
#if 0
215
static const char class_added[] = "added";
216
static const char class_changed[] = "changed";
217
static const char class_removed[] = "removed";
218
#endif
219
220
/* Per-character attributes.  */
221
enum
222
{
223
  ATTR_ESCAPE_SEQUENCE          = 1 << 0,
224
  /* The following two are exclusive.  */
225
  ATTR_FORMAT_DIRECTIVE         = 1 << 1,
226
  ATTR_INVALID_FORMAT_DIRECTIVE = 1 << 2
227
};
228
229
230
/* ================ Output parts of a message, as comments. ================ */
231
232
233
/* Output mp->comment as a set of comment lines.  */
234
235
static bool print_comment = true;
236
237
void
238
message_print_style_comment (bool flag)
239
0
{
240
0
  print_comment = flag;
241
0
}
242
243
void
244
message_print_comment (const message_ty *mp, ostream_t stream)
245
0
{
246
0
  if (print_comment && mp->comment != NULL)
247
0
    {
248
0
      begin_css_class (stream, class_translator_comment);
249
250
0
      for (size_t j = 0; j < mp->comment->nitems; ++j)
251
0
        {
252
0
          const char *s = mp->comment->item[j];
253
0
          do
254
0
            {
255
0
              ostream_write_str (stream, "#");
256
0
              if (*s != '\0')
257
0
                ostream_write_str (stream, " ");
258
0
              const char *e = strchr (s, '\n');
259
0
              if (e == NULL)
260
0
                {
261
0
                  ostream_write_str (stream, s);
262
0
                  s = NULL;
263
0
                }
264
0
              else
265
0
                {
266
0
                  ostream_write_mem (stream, s, e - s);
267
0
                  s = e + 1;
268
0
                }
269
0
              ostream_write_str (stream, "\n");
270
0
            }
271
0
          while (s != NULL);
272
0
        }
273
274
0
      end_css_class (stream, class_translator_comment);
275
0
    }
276
0
}
277
278
279
/* Output mp->comment_dot as a set of comment lines.  */
280
281
void
282
message_print_comment_dot (const message_ty *mp, ostream_t stream)
283
0
{
284
0
  if (mp->comment_dot != NULL)
285
0
    {
286
0
      begin_css_class (stream, class_extracted_comment);
287
288
0
      for (size_t j = 0; j < mp->comment_dot->nitems; ++j)
289
0
        {
290
0
          const char *s = mp->comment_dot->item[j];
291
0
          ostream_write_str (stream, "#.");
292
0
          if (*s != '\0')
293
0
            ostream_write_str (stream, " ");
294
0
          ostream_write_str (stream, s);
295
0
          ostream_write_str (stream, "\n");
296
0
        }
297
298
0
      end_css_class (stream, class_extracted_comment);
299
0
    }
300
0
}
301
302
303
/* Output mp->filepos as a set of comment lines.  */
304
305
static enum filepos_comment_type filepos_comment_type = filepos_comment_full;
306
307
void
308
message_print_comment_filepos (const message_ty *mp, ostream_t stream,
309
                               const char *charset, bool uniforum,
310
                               size_t page_width)
311
0
{
312
0
  if (filepos_comment_type != filepos_comment_none
313
0
      && mp->filepos_count != 0)
314
0
    {
315
0
      begin_css_class (stream, class_reference_comment);
316
317
0
      lex_pos_ty *filepos;
318
0
      size_t filepos_count;
319
0
      if (filepos_comment_type == filepos_comment_file)
320
0
        {
321
0
          filepos_count = 0;
322
0
          filepos = XNMALLOC (mp->filepos_count, lex_pos_ty);
323
324
0
          for (size_t i = 0; i < mp->filepos_count; ++i)
325
0
            {
326
0
              lex_pos_ty *pp = &mp->filepos[i];
327
328
0
              size_t j;
329
0
              for (j = 0; j < filepos_count; j++)
330
0
                if (strcmp (filepos[j].file_name, pp->file_name) == 0)
331
0
                  break;
332
333
0
              if (j == filepos_count)
334
0
                {
335
0
                  filepos[filepos_count].file_name = pp->file_name;
336
0
                  filepos[filepos_count].line_number = (size_t)-1;
337
0
                  filepos_count++;
338
0
                }
339
0
            }
340
0
        }
341
0
      else
342
0
        {
343
0
          filepos = mp->filepos;
344
0
          filepos_count = mp->filepos_count;
345
0
        }
346
347
0
      if (uniforum)
348
0
        {
349
0
          for (size_t j = 0; j < filepos_count; ++j)
350
0
            {
351
0
              lex_pos_ty *pp = &filepos[j];
352
353
0
              const char *cp = pp->file_name;
354
0
              while (cp[0] == '.' && cp[1] == '/')
355
0
                cp += 2;
356
357
0
              ostream_write_str (stream, "# ");
358
0
              begin_css_class (stream, class_reference);
359
              /* There are two Sun formats to choose from: SunOS and
360
                 Solaris.  Use the Solaris form here.  */
361
0
              char *str = xasprintf ("File: %s, line: %ld",
362
0
                                     cp, (long) pp->line_number);
363
0
              assume (str != NULL);
364
0
              ostream_write_str (stream, str);
365
0
              end_css_class (stream, class_reference);
366
0
              ostream_write_str (stream, "\n");
367
0
              free (str);
368
0
            }
369
0
        }
370
0
      else
371
0
        {
372
0
          const char *canon_charset = po_charset_canonicalize (charset);
373
374
0
          ostream_write_str (stream, "#:");
375
0
          size_t column = 2;
376
0
          for (size_t j = 0; j < filepos_count; ++j)
377
0
            {
378
0
              lex_pos_ty *pp = &filepos[j];
379
380
0
              const char *cp = pp->file_name;
381
0
              while (cp[0] == '.' && cp[1] == '/')
382
0
                cp += 2;
383
384
0
              char buffer[22];
385
0
              if (filepos_comment_type == filepos_comment_file
386
                  /* Some xgettext input formats, like RST, lack line
387
                     numbers.  */
388
0
                  || pp->line_number == (size_t)(-1))
389
0
                buffer[0] = '\0';
390
0
              else
391
0
                sprintf (buffer, ":%ld", (long) pp->line_number);
392
393
              /* File names are usually entirely ASCII.  Therefore strlen is
394
                 sufficient to determine their printed width.  */
395
0
              size_t width = strlen (cp) + strlen (buffer) + 1;
396
0
              if (column > 2 && column + width > page_width)
397
0
                {
398
0
                  ostream_write_str (stream, "\n#:");
399
0
                  column = 2;
400
0
                }
401
0
              ostream_write_str (stream, " ");
402
0
              begin_css_class (stream, class_reference);
403
0
              if (pos_filename_has_spaces (pp))
404
0
                {
405
                  /* Enclose the file name within U+2068 and U+2069 characters,
406
                     so that it can be parsed unambiguously.  */
407
0
                  if (canon_charset == po_charset_utf8)
408
0
                    {
409
0
                      ostream_write_str (stream, "\xE2\x81\xA8"); /* U+2068 */
410
0
                      ostream_write_str (stream, cp);
411
0
                      ostream_write_str (stream, "\xE2\x81\xA9"); /* U+2069 */
412
0
                    }
413
0
                  else if (canon_charset != NULL
414
0
                           && strcmp (canon_charset, "GB18030") == 0)
415
0
                    {
416
0
                      ostream_write_str (stream, "\x81\x36\xAC\x34"); /* U+2068 */
417
0
                      ostream_write_str (stream, cp);
418
0
                      ostream_write_str (stream, "\x81\x36\xAC\x35"); /* U+2069 */
419
0
                    }
420
0
                  else
421
0
                    abort ();
422
0
                }
423
0
              else
424
0
                ostream_write_str (stream, cp);
425
0
              ostream_write_str (stream, buffer);
426
0
              end_css_class (stream, class_reference);
427
0
              column += width;
428
0
            }
429
0
          ostream_write_str (stream, "\n");
430
0
        }
431
432
0
      if (filepos != mp->filepos)
433
0
        free (filepos);
434
435
0
      end_css_class (stream, class_reference_comment);
436
0
    }
437
0
}
438
439
440
/* Output mp->is_fuzzy, mp->is_format, mp->range, mp->do_wrap as a comment
441
   line.  */
442
443
void
444
message_print_comment_flags (const message_ty *mp, ostream_t stream, bool debug)
445
0
{
446
0
  if ((mp->is_fuzzy && mp->msgstr[0] != '\0')
447
0
      || has_significant_format_p (mp->is_format)
448
0
      || has_range_p (mp->range)
449
0
      || mp->do_wrap == no)
450
0
    {
451
0
      bool first_flag = true;
452
453
0
      begin_css_class (stream, class_flag_comment);
454
455
0
      ostream_write_str (stream, "#,");
456
457
      /* We don't print the fuzzy flag if the msgstr is empty.  This
458
         might be introduced by the user but we want to normalize the
459
         output.  */
460
0
      if (mp->is_fuzzy && mp->msgstr[0] != '\0')
461
0
        {
462
0
          ostream_write_str (stream, " ");
463
0
          begin_css_class (stream, class_flag);
464
0
          begin_css_class (stream, class_fuzzy_flag);
465
0
          ostream_write_str (stream, "fuzzy");
466
0
          end_css_class (stream, class_fuzzy_flag);
467
0
          end_css_class (stream, class_flag);
468
0
          first_flag = false;
469
0
        }
470
471
0
      for (size_t i = 0; i < NFORMATS; i++)
472
0
        if (significant_format_p (mp->is_format[i]))
473
0
          {
474
0
            if (!first_flag)
475
0
              ostream_write_str (stream, ",");
476
477
0
            ostream_write_str (stream, " ");
478
0
            begin_css_class (stream, class_flag);
479
0
            char *string =
480
0
              make_format_description_string (mp->is_format[i],
481
0
                                              format_language[i], debug);
482
0
            ostream_write_str (stream, string);
483
0
            free (string);
484
0
            end_css_class (stream, class_flag);
485
0
            first_flag = false;
486
0
          }
487
488
0
      if (has_range_p (mp->range))
489
0
        {
490
0
          if (!first_flag)
491
0
            ostream_write_str (stream, ",");
492
493
0
          ostream_write_str (stream, " ");
494
0
          begin_css_class (stream, class_flag);
495
0
          char *string = make_range_description_string (mp->range);
496
0
          ostream_write_str (stream, string);
497
0
          free (string);
498
0
          end_css_class (stream, class_flag);
499
0
          first_flag = false;
500
0
        }
501
502
0
      if (mp->do_wrap == no)
503
0
        {
504
0
          if (!first_flag)
505
0
            ostream_write_str (stream, ",");
506
507
0
          ostream_write_str (stream, " ");
508
0
          begin_css_class (stream, class_flag);
509
0
          ostream_write_str (stream,
510
0
                             make_c_width_description_string (mp->do_wrap));
511
0
          end_css_class (stream, class_flag);
512
0
          first_flag = false;
513
0
        }
514
515
0
      ostream_write_str (stream, "\n");
516
517
0
      end_css_class (stream, class_flag_comment);
518
0
    }
519
0
}
520
521
522
/* ========= Some parameters for use by 'msgdomain_list_print_po'. ========= */
523
524
525
/* This variable controls the extent to which the page width applies.
526
   True means it applies to message strings and file reference lines.
527
   False means it applies to file reference lines only.  */
528
static bool wrap_strings = true;
529
530
void
531
message_page_width_ignore ()
532
0
{
533
0
  wrap_strings = false;
534
0
}
535
536
537
/* These three variables control the output style of the message_print
538
   function.  Interface functions for them are to be used.  */
539
static bool indent = false;
540
static bool uniforum = false;
541
static bool escape = false;
542
543
void
544
message_print_style_indent ()
545
0
{
546
0
  indent = true;
547
0
}
548
549
void
550
message_print_style_uniforum ()
551
0
{
552
0
  uniforum = true;
553
0
}
554
555
void
556
message_print_style_escape (bool flag)
557
0
{
558
0
  escape = flag;
559
0
}
560
561
void
562
message_print_style_filepos (enum filepos_comment_type type)
563
0
{
564
0
  filepos_comment_type = type;
565
0
}
566
567
568
/* --add-location argument handling.  Return an error indicator.  */
569
bool
570
handle_filepos_comment_option (const char *option)
571
0
{
572
0
  if (option != NULL)
573
0
    {
574
0
      if (strcmp (option, "never") == 0 || strcmp (option, "no") == 0)
575
0
        message_print_style_filepos (filepos_comment_none);
576
0
      else if (strcmp (option, "full") == 0 || strcmp (option, "yes") == 0)
577
0
        message_print_style_filepos (filepos_comment_full);
578
0
      else if (strcmp (option, "file") == 0)
579
0
        message_print_style_filepos (filepos_comment_file);
580
0
      else
581
0
        {
582
0
          fprintf (stderr, "invalid --add-location argument: %s\n", option);
583
0
          return true;
584
0
        }
585
0
    }
586
0
  else
587
    /* --add-location is equivalent to --add-location=full.  */
588
0
    message_print_style_filepos (filepos_comment_full);
589
0
  return false;
590
0
}
591
592
593
/* =============== msgdomain_list_print_po() and subroutines. =============== */
594
595
596
/* A version of memcpy optimized for the case n <= 1.  */
597
static inline void
598
memcpy_small (void *dst, const void *src, size_t n)
599
0
{
600
0
  if (n > 0)
601
0
    {
602
0
      char *q = (char *) dst;
603
0
      const char *p = (const char *) src;
604
605
0
      *q = *p;
606
0
      if (--n > 0)
607
0
        do *++q = *++p; while (--n > 0);
608
0
    }
609
0
}
610
611
612
/* A version of memset optimized for the case n <= 1.  */
613
/* Avoid false GCC warning "‘__builtin_memset’ specified bound
614
   18446744073709551614 exceeds maximum object size 9223372036854775807."
615
   Cf. <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=109995>.  */
616
#if __GNUC__ >= 7
617
# pragma GCC diagnostic ignored "-Wstringop-overflow"
618
#endif
619
static inline void
620
memset_small (void *dst, char c, size_t n)
621
0
{
622
0
  if (n > 0)
623
0
    {
624
0
      char *p = (char *) dst;
625
626
0
      *p = c;
627
0
      if (--n > 0)
628
0
        do *++p = c; while (--n > 0);
629
0
    }
630
0
}
631
632
633
static void
634
wrap (const message_ty *mp, ostream_t stream,
635
      const char *line_prefix, int extra_indent, const char *css_class,
636
      const char *name, const char *value,
637
      enum is_wrap do_wrap, size_t page_width,
638
      const char *charset, xerror_handler_ty xeh)
639
0
{
640
0
  const char *canon_charset = po_charset_canonicalize (charset);
641
642
0
  bool weird_cjk;
643
0
#if HAVE_ICONV
644
  /* The old Solaris/openwin msgfmt and GNU msgfmt <= 0.10.35 don't know
645
     about multibyte encodings, and require a spurious backslash after
646
     every multibyte character whose last byte is 0x5C.  Some programs,
647
     like vim, distribute PO files in this broken format.  It is important
648
     for such programs that GNU msgmerge continues to support this old
649
     PO file format when the Makefile requests it.  */
650
0
  iconv_t conv;
651
0
  {
652
0
    const char *envval = getenv ("OLD_PO_FILE_OUTPUT");
653
0
    if (envval != NULL && *envval != '\0')
654
      /* Write a PO file in old format, with extraneous backslashes.  */
655
0
      conv = (iconv_t)(-1);
656
0
    else
657
0
      if (canon_charset == NULL)
658
        /* Invalid PO file encoding.  */
659
0
        conv = (iconv_t)(-1);
660
0
      else
661
        /* Use iconv() to parse multibyte characters.  */
662
0
        conv = iconv_open ("UTF-8", canon_charset);
663
0
  }
664
665
0
  if (conv != (iconv_t)(-1))
666
0
    weird_cjk = false;
667
0
  else
668
0
#endif
669
0
    if (canon_charset == NULL)
670
0
      weird_cjk = false;
671
0
    else
672
0
      weird_cjk = po_is_charset_weird_cjk (canon_charset);
673
674
0
  if (canon_charset == NULL)
675
0
    canon_charset = po_charset_ascii;
676
677
  /* Determine the extent of format string directives.  */
678
0
  char *fmtdir = NULL;
679
0
  char *fmtdirattr = NULL;
680
0
  if (value[0] != '\0')
681
0
    {
682
0
      bool is_msgstr =
683
0
        (strlen (name) >= 6 && memcmp (name, "msgstr", 6) == 0);
684
        /* or equivalent: = (css_class == class_msgstr) */
685
686
0
      for (size_t i = 0; i < NFORMATS; i++)
687
0
        if (possible_format_p (mp->is_format[i]))
688
0
          {
689
0
            size_t len = strlen (value);
690
0
            struct formatstring_parser *parser = formatstring_parsers[i];
691
692
0
            fmtdir = XCALLOC (len, char);
693
694
0
            char *invalid_reason = NULL;
695
0
            void *descr = parser->parse (value, is_msgstr, fmtdir, &invalid_reason);
696
0
            if (descr != NULL)
697
0
              parser->free (descr);
698
699
            /* Locate the FMTDIR_* bits and transform the array to an array
700
               of attributes.  */
701
0
            fmtdirattr = XCALLOC (len, char);
702
0
            const char *fd_end = fmtdir + len;
703
0
            const char *fdp;
704
0
            char *fdap;
705
0
            for (fdp = fmtdir, fdap = fmtdirattr; fdp < fd_end; fdp++, fdap++)
706
0
              if (*fdp & FMTDIR_START)
707
0
                {
708
0
                  const char *fdq;
709
0
                  for (fdq = fdp; fdq < fd_end; fdq++)
710
0
                    if (*fdq & (FMTDIR_END | FMTDIR_ERROR))
711
0
                      break;
712
0
                  if (!(fdq < fd_end))
713
                    /* The ->parse method has determined the start of a
714
                       formatstring directive but not stored a bit indicating
715
                       its end. It is a bug in the ->parse method.  */
716
0
                    abort ();
717
0
                  if (*fdq & FMTDIR_ERROR)
718
0
                    memset (fdap, ATTR_INVALID_FORMAT_DIRECTIVE, fdq - fdp + 1);
719
0
                  else
720
0
                    memset (fdap, ATTR_FORMAT_DIRECTIVE, fdq - fdp + 1);
721
0
                  fdap += fdq - fdp;
722
0
                  fdp = fdq;
723
0
                }
724
0
              else
725
0
                *fdap = 0;
726
727
0
            break;
728
0
          }
729
0
    }
730
731
  /* Loop over the '\n' delimited portions of value.  */
732
0
  const char *s = value;
733
0
  bool first_line = true;
734
0
  do
735
0
    {
736
      /* The usual escapes, as defined by the ANSI C Standard.  */
737
0
#     define is_escape(c) \
738
0
        ((c) == '\a' || (c) == '\b' || (c) == '\f' || (c) == '\n' \
739
0
         || (c) == '\r' || (c) == '\t' || (c) == '\v')
740
741
0
      const char *es;
742
0
      for (es = s; *es != '\0'; )
743
0
        if (*es++ == '\n')
744
0
          break;
745
746
      /* Expand escape sequences in each portion.  */
747
0
      const char *ep;
748
0
      size_t portion_len;
749
0
      for (ep = s, portion_len = 0; ep < es; ep++)
750
0
        {
751
0
          char c = *ep;
752
0
          if (is_escape (c))
753
0
            portion_len += 2;
754
0
          else if (escape && !c_isprint ((unsigned char) c))
755
0
            portion_len += 4;
756
0
          else if (c == '\\' || c == '"')
757
0
            portion_len += 2;
758
0
          else
759
0
            {
760
0
#if HAVE_ICONV
761
0
              if (conv != (iconv_t)(-1))
762
0
                {
763
                  /* Skip over a complete multi-byte character.  Don't
764
                     interpret the second byte of a multi-byte character as
765
                     ASCII.  This is needed for the BIG5, BIG5-HKSCS, GBK,
766
                     GB18030, SHIFT_JIS, JOHAB encodings.  */
767
0
                  char scratchbuf[64];
768
0
                  const char *inptr = ep;
769
0
                  size_t insize;
770
0
                  char *outptr = &scratchbuf[0];
771
0
                  size_t outsize = sizeof (scratchbuf);
772
0
                  size_t res;
773
774
0
                  res = (size_t)(-1);
775
0
                  for (insize = 1; inptr + insize <= es; insize++)
776
0
                    {
777
0
                      res = iconv (conv,
778
0
                                   (ICONV_CONST char **) &inptr, &insize,
779
0
                                   &outptr, &outsize);
780
0
                      if (!(res == (size_t)(-1) && errno == EINVAL))
781
0
                        break;
782
                      /* We expect that no input bytes have been consumed
783
                         so far.  */
784
0
                      if (inptr != ep)
785
0
                        abort ();
786
0
                    }
787
0
                  if (res == (size_t)(-1))
788
0
                    {
789
0
                      if (errno == EILSEQ)
790
0
                        {
791
0
                          xeh->xerror (CAT_SEVERITY_ERROR, mp, NULL, 0, 0,
792
0
                                       false,
793
0
                                       _("invalid multibyte sequence"));
794
0
                          continue;
795
0
                        }
796
0
                      else if (errno == EINVAL)
797
0
                        {
798
                          /* This could happen if an incomplete
799
                             multibyte sequence at the end of input
800
                             bytes.  */
801
0
                          xeh->xerror (CAT_SEVERITY_ERROR, mp, NULL, 0, 0,
802
0
                                       false,
803
0
                                       _("incomplete multibyte sequence"));
804
0
                          continue;
805
0
                        }
806
0
                      else
807
0
                        abort ();
808
0
                    }
809
0
                  insize = inptr - ep;
810
0
                  portion_len += insize;
811
0
                  ep += insize - 1;
812
0
                }
813
0
              else
814
0
#endif
815
0
                {
816
0
                  if (weird_cjk
817
                      /* Special handling of encodings with CJK structure.  */
818
0
                      && ep + 2 <= es
819
0
                      && (unsigned char) ep[0] >= 0x80
820
0
                      && (unsigned char) ep[1] >= 0x30)
821
0
                    {
822
0
                      portion_len += 2;
823
0
                      ep += 1;
824
0
                    }
825
0
                  else
826
0
                    portion_len += 1;
827
0
                }
828
0
            }
829
0
        }
830
831
0
      char *portion = XNMALLOC (portion_len, char);
832
0
      char *overrides = XNMALLOC (portion_len, char);
833
0
      char *attributes = XNMALLOC (portion_len, char);
834
0
      char *pp;
835
0
      char *op;
836
0
      char *ap;
837
0
      for (ep = s, pp = portion, op = overrides, ap = attributes; ep < es; ep++)
838
0
        {
839
0
          char c = *ep;
840
0
          char attr = (fmtdirattr != NULL ? fmtdirattr[ep - value] : 0);
841
0
          char brk = UC_BREAK_UNDEFINED;
842
          /* Don't break inside format directives.  */
843
0
          if (attr == ATTR_FORMAT_DIRECTIVE
844
0
              && (fmtdir[ep - value] & FMTDIR_START) == 0)
845
0
            brk = UC_BREAK_PROHIBITED;
846
0
          if (is_escape (c))
847
0
            {
848
0
              switch (c)
849
0
                {
850
0
                case '\a': c = 'a'; break;
851
0
                case '\b': c = 'b'; break;
852
0
                case '\f': c = 'f'; break;
853
0
                case '\n': c = 'n'; break;
854
0
                case '\r': c = 'r'; break;
855
0
                case '\t': c = 't'; break;
856
0
                case '\v': c = 'v'; break;
857
0
                default: abort ();
858
0
                }
859
0
              *pp++ = '\\';
860
0
              *pp++ = c;
861
0
              *op++ = brk;
862
0
              *op++ = UC_BREAK_PROHIBITED;
863
0
              *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
864
0
              *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
865
              /* We warn about any use of escape sequences beside
866
                 '\n' and '\t'.  */
867
0
              if (c != 'n' && c != 't')
868
0
                {
869
0
                  char *error_message =
870
0
                    xasprintf (_("internationalized messages should not contain the '\\%c' escape sequence"),
871
0
                               c);
872
0
                  xeh->xerror (CAT_SEVERITY_WARNING, mp, NULL, 0, 0, false,
873
0
                               error_message);
874
0
                  free (error_message);
875
0
                }
876
0
            }
877
0
          else if (escape && !c_isprint ((unsigned char) c))
878
0
            {
879
0
              *pp++ = '\\';
880
0
              *pp++ = '0' + (((unsigned char) c >> 6) & 7);
881
0
              *pp++ = '0' + (((unsigned char) c >> 3) & 7);
882
0
              *pp++ = '0' + ((unsigned char) c & 7);
883
0
              *op++ = brk;
884
0
              *op++ = UC_BREAK_PROHIBITED;
885
0
              *op++ = UC_BREAK_PROHIBITED;
886
0
              *op++ = UC_BREAK_PROHIBITED;
887
0
              *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
888
0
              *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
889
0
              *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
890
0
              *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
891
0
            }
892
0
          else if (c == '\\' || c == '"')
893
0
            {
894
0
              *pp++ = '\\';
895
0
              *pp++ = c;
896
0
              *op++ = brk;
897
0
              *op++ = UC_BREAK_PROHIBITED;
898
0
              *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
899
0
              *ap++ = attr | ATTR_ESCAPE_SEQUENCE;
900
0
            }
901
0
          else
902
0
            {
903
0
#if HAVE_ICONV
904
0
              if (conv != (iconv_t)(-1))
905
0
                {
906
                  /* Copy a complete multi-byte character.  Don't
907
                     interpret the second byte of a multi-byte character as
908
                     ASCII.  This is needed for the BIG5, BIG5-HKSCS, GBK,
909
                     GB18030, SHIFT_JIS, JOHAB encodings.  */
910
0
                  char scratchbuf[64];
911
0
                  const char *inptr = ep;
912
0
                  size_t insize;
913
0
                  char *outptr = &scratchbuf[0];
914
0
                  size_t outsize = sizeof (scratchbuf);
915
0
                  size_t res;
916
917
0
                  res = (size_t)(-1);
918
0
                  for (insize = 1; inptr + insize <= es; insize++)
919
0
                    {
920
0
                      res = iconv (conv,
921
0
                                   (ICONV_CONST char **) &inptr, &insize,
922
0
                                   &outptr, &outsize);
923
0
                      if (!(res == (size_t)(-1) && errno == EINVAL))
924
0
                        break;
925
                      /* We expect that no input bytes have been consumed
926
                         so far.  */
927
0
                      if (inptr != ep)
928
0
                        abort ();
929
0
                    }
930
0
                  if (res == (size_t)(-1))
931
0
                    {
932
0
                      if (errno == EILSEQ)
933
0
                        {
934
0
                          xeh->xerror (CAT_SEVERITY_ERROR, mp, NULL, 0, 0,
935
0
                                       false, _("invalid multibyte sequence"));
936
0
                          continue;
937
0
                        }
938
0
                      else
939
0
                        abort ();
940
0
                    }
941
0
                  insize = inptr - ep;
942
0
                  memcpy_small (pp, ep, insize);
943
0
                  pp += insize;
944
0
                  *op = brk;
945
0
                  memset_small (op + 1, UC_BREAK_PROHIBITED, insize - 1);
946
0
                  op += insize;
947
0
                  memset_small (ap, attr, insize);
948
0
                  ap += insize;
949
0
                  ep += insize - 1;
950
0
                }
951
0
              else
952
0
#endif
953
0
                {
954
0
                  if (weird_cjk
955
                      /* Special handling of encodings with CJK structure.  */
956
0
                      && ep + 2 <= es
957
0
                      && (unsigned char) c >= 0x80
958
0
                      && (unsigned char) ep[1] >= 0x30)
959
0
                    {
960
0
                      *pp++ = c;
961
0
                      ep += 1;
962
0
                      *pp++ = *ep;
963
0
                      *op++ = brk;
964
0
                      *op++ = UC_BREAK_PROHIBITED;
965
0
                      *ap++ = attr;
966
0
                      *ap++ = attr;
967
0
                    }
968
0
                  else
969
0
                    {
970
0
                      *pp++ = c;
971
0
                      *op++ = brk;
972
0
                      *ap++ = attr;
973
0
                    }
974
0
                }
975
0
            }
976
0
        }
977
978
      /* Don't break immediately before the "\n" at the end.  */
979
0
      if (es > s && es[-1] == '\n')
980
0
        overrides[portion_len - 2] = UC_BREAK_PROHIBITED;
981
982
0
      char *linebreaks = XNMALLOC (portion_len, char);
983
984
      /* Subsequent lines after a break are all indented.
985
         See INDENT-S.  */
986
0
      int startcol_after_break = (line_prefix ? strlen (line_prefix) : 0);
987
0
      if (indent)
988
0
        startcol_after_break = (startcol_after_break + extra_indent + 8) & ~7;
989
0
      startcol_after_break++;
990
991
      /* The line width.  Allow room for the closing quote character.  */
992
0
      int width = (wrap_strings && do_wrap != no ? page_width : INT_MAX) - 1;
993
      /* Adjust for indentation of subsequent lines.  */
994
0
      width -= startcol_after_break;
995
996
0
    recompute: ;
997
      /* The line starts with different things depending on whether it
998
         is the first line, and if we are using the indented style.
999
         See INDENT-F.  */
1000
0
      int startcol = (line_prefix ? strlen (line_prefix) : 0);
1001
0
      if (first_line)
1002
0
        {
1003
0
          startcol += strlen (name);
1004
0
          if (indent)
1005
0
            startcol = (startcol + extra_indent + 8) & ~7;
1006
0
          else
1007
0
            startcol++;
1008
0
        }
1009
0
      else
1010
0
        {
1011
0
          if (indent)
1012
0
            startcol = (startcol + extra_indent + 8) & ~7;
1013
0
        }
1014
      /* Allow room for the opening quote character.  */
1015
0
      startcol++;
1016
      /* Adjust for indentation of subsequent lines.  */
1017
0
      startcol -= startcol_after_break;
1018
1019
      /* Do line breaking on the portion.  */
1020
0
      ulc_width_linebreaks (portion, portion_len, width, startcol, 0,
1021
0
                            overrides, canon_charset, linebreaks);
1022
1023
      /* If this is the first line, and we are not using the indented
1024
         style, and the line would wrap, then use an empty first line
1025
         and restart.  */
1026
0
      if (first_line && !indent
1027
0
          && portion_len > 0
1028
0
          && (*es != '\0'
1029
0
              || startcol > width
1030
0
              || memchr (linebreaks, UC_BREAK_POSSIBLE, portion_len) != NULL))
1031
0
        {
1032
0
          if (line_prefix != NULL)
1033
0
            ostream_write_str (stream, line_prefix);
1034
0
          begin_css_class (stream, css_class);
1035
0
          begin_css_class (stream, class_keyword);
1036
0
          ostream_write_str (stream, name);
1037
0
          end_css_class (stream, class_keyword);
1038
0
          ostream_write_str (stream, " ");
1039
0
          begin_css_class (stream, class_string);
1040
0
          ostream_write_str (stream, "\"\"");
1041
0
          end_css_class (stream, class_string);
1042
0
          end_css_class (stream, css_class);
1043
0
          ostream_write_str (stream, "\n");
1044
0
          first_line = false;
1045
          /* Recompute startcol and linebreaks.  */
1046
0
          goto recompute;
1047
0
        }
1048
1049
      /* Print the beginning of the line.  This will depend on whether
1050
         this is the first line, and if the indented style is being
1051
         used.  INDENT-F.  */
1052
0
      {
1053
0
        int currcol = 0;
1054
1055
0
        if (line_prefix != NULL)
1056
0
          {
1057
0
            ostream_write_str (stream, line_prefix);
1058
0
            currcol = strlen (line_prefix);
1059
0
          }
1060
0
        begin_css_class (stream, css_class);
1061
0
        if (first_line)
1062
0
          {
1063
0
            begin_css_class (stream, class_keyword);
1064
0
            ostream_write_str (stream, name);
1065
0
            currcol += strlen (name);
1066
0
            end_css_class (stream, class_keyword);
1067
0
            if (indent)
1068
0
              {
1069
0
                if (extra_indent > 0)
1070
0
                  ostream_write_mem (stream, "        ", extra_indent);
1071
0
                currcol += extra_indent;
1072
0
                ostream_write_mem (stream, "        ", 8 - (currcol & 7));
1073
0
                currcol = (currcol + 8) & ~7;
1074
0
              }
1075
0
            else
1076
0
              {
1077
0
                ostream_write_str (stream, " ");
1078
0
                currcol++;
1079
0
              }
1080
0
            first_line = false;
1081
0
          }
1082
0
        else
1083
0
          {
1084
0
            if (indent)
1085
0
              {
1086
0
                if (extra_indent > 0)
1087
0
                  ostream_write_mem (stream, "        ", extra_indent);
1088
0
                currcol += extra_indent;
1089
0
                ostream_write_mem (stream, "        ", 8 - (currcol & 7));
1090
0
                currcol = (currcol + 8) & ~7;
1091
0
              }
1092
0
          }
1093
0
      }
1094
1095
      /* Print the portion itself, with linebreaks where necessary.  */
1096
0
      {
1097
0
        begin_css_class (stream, class_string);
1098
0
        ostream_write_str (stream, "\"");
1099
0
        begin_css_class (stream, class_text);
1100
1101
0
        char currattr = 0;
1102
1103
0
        for (size_t i = 0; i < portion_len; i++)
1104
0
          {
1105
0
            if (linebreaks[i] == UC_BREAK_POSSIBLE)
1106
0
              {
1107
                /* Change currattr so that it becomes 0.  */
1108
0
                if (currattr & ATTR_ESCAPE_SEQUENCE)
1109
0
                  {
1110
0
                    end_css_class (stream, class_escape_sequence);
1111
0
                    currattr &= ~ATTR_ESCAPE_SEQUENCE;
1112
0
                  }
1113
0
                if (currattr & ATTR_FORMAT_DIRECTIVE)
1114
0
                  {
1115
0
                    end_css_class (stream, class_format_directive);
1116
0
                    currattr &= ~ATTR_FORMAT_DIRECTIVE;
1117
0
                  }
1118
0
                else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1119
0
                  {
1120
0
                    end_css_class (stream, class_invalid_format_directive);
1121
0
                    currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE;
1122
0
                  }
1123
0
                if (!(currattr == 0))
1124
0
                  abort ();
1125
1126
0
                end_css_class (stream, class_text);
1127
0
                ostream_write_str (stream, "\"");
1128
0
                end_css_class (stream, class_string);
1129
0
                end_css_class (stream, css_class);
1130
0
                ostream_write_str (stream, "\n");
1131
0
                int currcol = 0;
1132
                /* INDENT-S.  */
1133
0
                if (line_prefix != NULL)
1134
0
                  {
1135
0
                    ostream_write_str (stream, line_prefix);
1136
0
                    currcol = strlen (line_prefix);
1137
0
                  }
1138
0
                begin_css_class (stream, css_class);
1139
0
                if (indent)
1140
0
                  {
1141
0
                    ostream_write_mem (stream, "        ", 8 - (currcol & 7));
1142
0
                    currcol = (currcol + 8) & ~7;
1143
0
                  }
1144
0
                begin_css_class (stream, class_string);
1145
0
                ostream_write_str (stream, "\"");
1146
0
                begin_css_class (stream, class_text);
1147
0
              }
1148
            /* Change currattr so that it matches attributes[i].  */
1149
0
            if (attributes[i] != currattr)
1150
0
              {
1151
                /* class_escape_sequence occurs inside class_format_directive
1152
                   and class_invalid_format_directive, so clear it first.  */
1153
0
                if (currattr & ATTR_ESCAPE_SEQUENCE)
1154
0
                  {
1155
0
                    end_css_class (stream, class_escape_sequence);
1156
0
                    currattr &= ~ATTR_ESCAPE_SEQUENCE;
1157
0
                  }
1158
0
                if (~attributes[i] & currattr & ATTR_FORMAT_DIRECTIVE)
1159
0
                  {
1160
0
                    end_css_class (stream, class_format_directive);
1161
0
                    currattr &= ~ATTR_FORMAT_DIRECTIVE;
1162
0
                  }
1163
0
                else if (~attributes[i] & currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1164
0
                  {
1165
0
                    end_css_class (stream, class_invalid_format_directive);
1166
0
                    currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE;
1167
0
                  }
1168
0
                if (attributes[i] & ~currattr & ATTR_FORMAT_DIRECTIVE)
1169
0
                  {
1170
0
                    begin_css_class (stream, class_format_directive);
1171
0
                    currattr |= ATTR_FORMAT_DIRECTIVE;
1172
0
                  }
1173
0
                else if (attributes[i] & ~currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1174
0
                  {
1175
0
                    begin_css_class (stream, class_invalid_format_directive);
1176
0
                    currattr |= ATTR_INVALID_FORMAT_DIRECTIVE;
1177
0
                  }
1178
                /* class_escape_sequence occurs inside class_format_directive
1179
                   and class_invalid_format_directive, so set it last.  */
1180
0
                if (attributes[i] & ~currattr & ATTR_ESCAPE_SEQUENCE)
1181
0
                  {
1182
0
                    begin_css_class (stream, class_escape_sequence);
1183
0
                    currattr |= ATTR_ESCAPE_SEQUENCE;
1184
0
                  }
1185
0
              }
1186
0
            ostream_write_mem (stream, &portion[i], 1);
1187
0
          }
1188
1189
        /* Change currattr so that it becomes 0.  */
1190
0
        if (currattr & ATTR_ESCAPE_SEQUENCE)
1191
0
          {
1192
0
            end_css_class (stream, class_escape_sequence);
1193
0
            currattr &= ~ATTR_ESCAPE_SEQUENCE;
1194
0
          }
1195
0
        if (currattr & ATTR_FORMAT_DIRECTIVE)
1196
0
          {
1197
0
            end_css_class (stream, class_format_directive);
1198
0
            currattr &= ~ATTR_FORMAT_DIRECTIVE;
1199
0
          }
1200
0
        else if (currattr & ATTR_INVALID_FORMAT_DIRECTIVE)
1201
0
          {
1202
0
            end_css_class (stream, class_invalid_format_directive);
1203
0
            currattr &= ~ATTR_INVALID_FORMAT_DIRECTIVE;
1204
0
          }
1205
0
        if (!(currattr == 0))
1206
0
          abort ();
1207
1208
0
        end_css_class (stream, class_text);
1209
0
        ostream_write_str (stream, "\"");
1210
0
        end_css_class (stream, class_string);
1211
0
        end_css_class (stream, css_class);
1212
0
        ostream_write_str (stream, "\n");
1213
0
      }
1214
1215
0
      free (linebreaks);
1216
0
      free (attributes);
1217
0
      free (overrides);
1218
0
      free (portion);
1219
1220
0
      s = es;
1221
0
#     undef is_escape
1222
0
    }
1223
0
  while (*s);
1224
1225
0
  if (fmtdirattr != NULL)
1226
0
    free (fmtdirattr);
1227
0
  if (fmtdir != NULL)
1228
0
    free (fmtdir);
1229
1230
0
#if HAVE_ICONV
1231
0
  if (conv != (iconv_t)(-1))
1232
0
    iconv_close (conv);
1233
0
#endif
1234
0
}
1235
1236
1237
static void
1238
print_blank_line (ostream_t stream)
1239
0
{
1240
0
  if (uniforum)
1241
0
    {
1242
0
      begin_css_class (stream, class_comment);
1243
0
      ostream_write_str (stream, "#\n");
1244
0
      end_css_class (stream, class_comment);
1245
0
    }
1246
0
  else
1247
0
    ostream_write_str (stream, "\n");
1248
0
}
1249
1250
1251
static void
1252
message_print (const message_ty *mp, ostream_t stream,
1253
               const char *charset, size_t page_width, bool blank_line,
1254
               xerror_handler_ty xeh, bool debug)
1255
0
{
1256
  /* Separate messages with a blank line.  Uniforum doesn't like blank
1257
     lines, so use an empty comment (unless there already is one).  */
1258
0
  if (blank_line && (!uniforum
1259
0
                     || mp->comment == NULL
1260
0
                     || mp->comment->nitems == 0
1261
0
                     || mp->comment->item[0][0] != '\0'))
1262
0
    print_blank_line (stream);
1263
1264
0
  if (is_header (mp))
1265
0
    begin_css_class (stream, class_header);
1266
0
  else if (mp->msgstr[0] == '\0')
1267
0
    begin_css_class (stream, class_untranslated);
1268
0
  else if (mp->is_fuzzy)
1269
0
    begin_css_class (stream, class_fuzzy);
1270
0
  else
1271
0
    begin_css_class (stream, class_translated);
1272
1273
0
  begin_css_class (stream, class_comment);
1274
1275
  /* Print translator comment if available.  */
1276
0
  message_print_comment (mp, stream);
1277
1278
  /* Print xgettext extracted comments.  */
1279
0
  message_print_comment_dot (mp, stream);
1280
1281
  /* Print the file position comments.  This will help a human who is
1282
     trying to navigate the sources.  There is no problem of getting
1283
     repeated positions, because duplicates are checked for.  */
1284
0
  message_print_comment_filepos (mp, stream, charset, uniforum, page_width);
1285
1286
  /* Print flag information in special comment.  */
1287
0
  message_print_comment_flags (mp, stream, debug);
1288
1289
  /* Print the previous msgid.  This helps the translator when the msgid has
1290
     only slightly changed.  */
1291
0
  begin_css_class (stream, class_previous_comment);
1292
0
  if (mp->prev_msgctxt != NULL)
1293
0
    wrap (mp, stream, "#| ", 0, class_previous, "msgctxt", mp->prev_msgctxt,
1294
0
          mp->do_wrap, page_width, charset, xeh);
1295
0
  if (mp->prev_msgid != NULL)
1296
0
    wrap (mp, stream, "#| ", 0, class_previous, "msgid", mp->prev_msgid,
1297
0
          mp->do_wrap, page_width, charset, xeh);
1298
0
  if (mp->prev_msgid_plural != NULL)
1299
0
    wrap (mp, stream, "#| ", 0, class_previous, "msgid_plural",
1300
0
          mp->prev_msgid_plural, mp->do_wrap, page_width, charset, xeh);
1301
0
  end_css_class (stream, class_previous_comment);
1302
0
  int extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL
1303
0
                      || mp->prev_msgid_plural != NULL
1304
0
                      ? 3
1305
0
                      : 0);
1306
1307
0
  end_css_class (stream, class_comment);
1308
1309
  /* Print each of the message components.  Wrap them nicely so they
1310
     are as readable as possible.  If there is no recorded msgstr for
1311
     this domain, emit an empty string.  */
1312
0
  if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt)
1313
0
      && po_charset_canonicalize (charset) != po_charset_utf8)
1314
0
    {
1315
0
      char *warning_message =
1316
0
        xasprintf (_("\
1317
0
The following msgctxt contains non-ASCII characters.\n\
1318
0
This will cause problems to translators who use a character encoding\n\
1319
0
different from yours. Consider using a pure ASCII msgctxt instead.\n\
1320
0
%s\n"), mp->msgctxt);
1321
0
      xeh->xerror (CAT_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1322
0
      free (warning_message);
1323
0
    }
1324
0
  if (!is_ascii_string (mp->msgid)
1325
0
      && po_charset_canonicalize (charset) != po_charset_utf8)
1326
0
    {
1327
0
      char *warning_message =
1328
0
        xasprintf (_("\
1329
0
The following msgid contains non-ASCII characters.\n\
1330
0
This will cause problems to translators who use a character encoding\n\
1331
0
different from yours. Consider using a pure ASCII msgid instead.\n\
1332
0
%s\n"), mp->msgid);
1333
0
      xeh->xerror (CAT_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1334
0
      free (warning_message);
1335
0
    }
1336
0
  if (mp->msgctxt != NULL)
1337
0
    wrap (mp, stream, NULL, extra_indent, class_msgid, "msgctxt", mp->msgctxt,
1338
0
          mp->do_wrap, page_width, charset, xeh);
1339
0
  wrap (mp, stream, NULL, extra_indent, class_msgid, "msgid", mp->msgid,
1340
0
        mp->do_wrap, page_width, charset, xeh);
1341
0
  if (mp->msgid_plural != NULL)
1342
0
    wrap (mp, stream, NULL, extra_indent, class_msgid, "msgid_plural",
1343
0
          mp->msgid_plural, mp->do_wrap, page_width, charset, xeh);
1344
1345
0
  if (mp->msgid_plural == NULL)
1346
0
    wrap (mp, stream, NULL, extra_indent, class_msgstr, "msgstr", mp->msgstr,
1347
0
          mp->do_wrap, page_width, charset, xeh);
1348
0
  else
1349
0
    {
1350
0
      unsigned int i;
1351
0
      const char *p;
1352
0
      for (p = mp->msgstr, i = 0;
1353
0
           p < mp->msgstr + mp->msgstr_len;
1354
0
           p += strlen (p) + 1, i++)
1355
0
        {
1356
0
          char prefix_buf[20];
1357
0
          sprintf (prefix_buf, "msgstr[%u]", i);
1358
0
          wrap (mp, stream, NULL, extra_indent, class_msgstr, prefix_buf, p,
1359
0
                mp->do_wrap, page_width, charset, xeh);
1360
0
        }
1361
0
    }
1362
1363
0
  if (is_header (mp))
1364
0
    end_css_class (stream, class_header);
1365
0
  else if (mp->msgstr[0] == '\0')
1366
0
    end_css_class (stream, class_untranslated);
1367
0
  else if (mp->is_fuzzy)
1368
0
    end_css_class (stream, class_fuzzy);
1369
0
  else
1370
0
    end_css_class (stream, class_translated);
1371
0
}
1372
1373
1374
static void
1375
message_print_obsolete (const message_ty *mp, ostream_t stream,
1376
                        const char *charset, size_t page_width, bool blank_line,
1377
                        xerror_handler_ty xeh, bool debug)
1378
0
{
1379
  /* If msgstr is the empty string we print nothing.  */
1380
0
  if (mp->msgstr[0] == '\0')
1381
0
    return;
1382
1383
  /* Separate messages with a blank line.  Uniforum doesn't like blank
1384
     lines, so use an empty comment (unless there already is one).  */
1385
0
  if (blank_line)
1386
0
    print_blank_line (stream);
1387
1388
0
  begin_css_class (stream, class_obsolete);
1389
1390
0
  begin_css_class (stream, class_comment);
1391
1392
  /* Print translator comment if available.  */
1393
0
  message_print_comment (mp, stream);
1394
1395
  /* Print xgettext extracted comments (normally empty).  */
1396
0
  message_print_comment_dot (mp, stream);
1397
1398
  /* Print the file position comments (normally empty).  */
1399
0
  message_print_comment_filepos (mp, stream, charset, uniforum, page_width);
1400
1401
  /* Print flag information in special comment.
1402
     Preserve only
1403
       - the fuzzy flag, because it is important for the translator when the
1404
         message becomes active again,
1405
       - the no-wrap flag, because we use mp->do_wrap below for the wrapping,
1406
         therefore further processing through 'msgcat' needs to use the same
1407
         value of do_wrap,
1408
       - the *-format flags, because the wrapping depends on these flags (see
1409
         'Don't break inside format directives' comment), therefore further
1410
         processing through 'msgcat' needs to use the same values of is_format.
1411
     This is a trimmed-down variant of message_print_comment_flags.  */
1412
0
  if (mp->is_fuzzy
1413
0
      || has_significant_format_p (mp->is_format)
1414
0
      || mp->do_wrap == no)
1415
0
    {
1416
0
      bool first_flag = true;
1417
1418
0
      ostream_write_str (stream, "#,");
1419
1420
0
      if (mp->is_fuzzy)
1421
0
        {
1422
0
          ostream_write_str (stream, " fuzzy");
1423
0
          first_flag = false;
1424
0
        }
1425
1426
0
      for (size_t i = 0; i < NFORMATS; i++)
1427
0
        if (significant_format_p (mp->is_format[i]))
1428
0
          {
1429
0
            if (!first_flag)
1430
0
              ostream_write_str (stream, ",");
1431
1432
0
            ostream_write_str (stream, " ");
1433
0
            char *string =
1434
0
              make_format_description_string (mp->is_format[i],
1435
0
                                              format_language[i], debug);
1436
0
            ostream_write_str (stream, string);
1437
0
            free (string);
1438
0
            first_flag = false;
1439
0
          }
1440
1441
0
      if (mp->do_wrap == no)
1442
0
        {
1443
0
          if (!first_flag)
1444
0
            ostream_write_str (stream, ",");
1445
1446
0
          ostream_write_str (stream, " ");
1447
0
          ostream_write_str (stream,
1448
0
                             make_c_width_description_string (mp->do_wrap));
1449
0
          first_flag = false;
1450
0
        }
1451
1452
0
      ostream_write_str (stream, "\n");
1453
0
    }
1454
1455
  /* Print the previous msgid.  This helps the translator when the msgid has
1456
     only slightly changed.  */
1457
0
  begin_css_class (stream, class_previous_comment);
1458
0
  if (mp->prev_msgctxt != NULL)
1459
0
    wrap (mp, stream, "#~| ", 0, class_previous, "msgctxt", mp->prev_msgctxt,
1460
0
          mp->do_wrap, page_width, charset, xeh);
1461
0
  if (mp->prev_msgid != NULL)
1462
0
    wrap (mp, stream, "#~| ", 0, class_previous, "msgid", mp->prev_msgid,
1463
0
          mp->do_wrap, page_width, charset, xeh);
1464
0
  if (mp->prev_msgid_plural != NULL)
1465
0
    wrap (mp, stream, "#~| ", 0, class_previous, "msgid_plural",
1466
0
          mp->prev_msgid_plural, mp->do_wrap, page_width, charset, xeh);
1467
0
  end_css_class (stream, class_previous_comment);
1468
0
  int extra_indent = (mp->prev_msgctxt != NULL || mp->prev_msgid != NULL
1469
0
                      || mp->prev_msgid_plural != NULL
1470
0
                      ? 1
1471
0
                      : 0);
1472
1473
0
  end_css_class (stream, class_comment);
1474
1475
  /* Print each of the message components.  Wrap them nicely so they
1476
     are as readable as possible.  */
1477
0
  if (mp->msgctxt != NULL && !is_ascii_string (mp->msgctxt)
1478
0
      && po_charset_canonicalize (charset) != po_charset_utf8)
1479
0
    {
1480
0
      char *warning_message =
1481
0
        xasprintf (_("\
1482
0
The following msgctxt contains non-ASCII characters.\n\
1483
0
This will cause problems to translators who use a character encoding\n\
1484
0
different from yours. Consider using a pure ASCII msgctxt instead.\n\
1485
0
%s\n"), mp->msgctxt);
1486
0
      xeh->xerror (CAT_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1487
0
      free (warning_message);
1488
0
    }
1489
0
  if (!is_ascii_string (mp->msgid)
1490
0
      && po_charset_canonicalize (charset) != po_charset_utf8)
1491
0
    {
1492
0
      char *warning_message =
1493
0
        xasprintf (_("\
1494
0
The following msgid contains non-ASCII characters.\n\
1495
0
This will cause problems to translators who use a character encoding\n\
1496
0
different from yours. Consider using a pure ASCII msgid instead.\n\
1497
0
%s\n"), mp->msgid);
1498
0
      xeh->xerror (CAT_SEVERITY_WARNING, mp, NULL, 0, 0, true, warning_message);
1499
0
      free (warning_message);
1500
0
    }
1501
0
  if (mp->msgctxt != NULL)
1502
0
    wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgctxt", mp->msgctxt,
1503
0
          mp->do_wrap, page_width, charset, xeh);
1504
0
  wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgid", mp->msgid,
1505
0
        mp->do_wrap, page_width, charset, xeh);
1506
0
  if (mp->msgid_plural != NULL)
1507
0
    wrap (mp, stream, "#~ ", extra_indent, class_msgid, "msgid_plural",
1508
0
          mp->msgid_plural, mp->do_wrap, page_width, charset, xeh);
1509
1510
0
  if (mp->msgid_plural == NULL)
1511
0
    wrap (mp, stream, "#~ ", extra_indent, class_msgstr, "msgstr", mp->msgstr,
1512
0
          mp->do_wrap, page_width, charset, xeh);
1513
0
  else
1514
0
    {
1515
0
      unsigned int i;
1516
0
      const char *p;
1517
0
      for (p = mp->msgstr, i = 0;
1518
0
           p < mp->msgstr + mp->msgstr_len;
1519
0
           p += strlen (p) + 1, i++)
1520
0
        {
1521
0
          char prefix_buf[20];
1522
0
          sprintf (prefix_buf, "msgstr[%u]", i);
1523
0
          wrap (mp, stream, "#~ ", extra_indent, class_msgstr, prefix_buf, p,
1524
0
                mp->do_wrap, page_width, charset, xeh);
1525
0
        }
1526
0
    }
1527
1528
0
  end_css_class (stream, class_obsolete);
1529
0
}
1530
1531
1532
static void
1533
msgdomain_list_print_po (msgdomain_list_ty *mdlp, ostream_t stream,
1534
                         size_t page_width, xerror_handler_ty xeh, bool debug)
1535
0
{
1536
  /* Write out the messages for each domain.  */
1537
0
  bool blank_line = false;
1538
0
  for (size_t k = 0; k < mdlp->nitems; k++)
1539
0
    {
1540
      /* If the first domain is the default, don't bother emitting
1541
         the domain name, because it is the default.  */
1542
0
      if (!(k == 0
1543
0
            && strcmp (mdlp->item[k]->domain, MESSAGE_DOMAIN_DEFAULT) == 0))
1544
0
        {
1545
0
          if (blank_line)
1546
0
            print_blank_line (stream);
1547
0
          begin_css_class (stream, class_keyword);
1548
0
          ostream_write_str (stream, "domain");
1549
0
          end_css_class (stream, class_keyword);
1550
0
          ostream_write_str (stream, " ");
1551
0
          begin_css_class (stream, class_string);
1552
0
          ostream_write_str (stream, "\"");
1553
0
          begin_css_class (stream, class_text);
1554
0
          ostream_write_str (stream, mdlp->item[k]->domain);
1555
0
          end_css_class (stream, class_text);
1556
0
          ostream_write_str (stream, "\"");
1557
0
          end_css_class (stream, class_string);
1558
0
          ostream_write_str (stream, "\n");
1559
0
          blank_line = true;
1560
0
        }
1561
1562
0
      message_list_ty *mlp = mdlp->item[k]->messages;
1563
1564
      /* Search the header entry.  */
1565
0
      const char *header = NULL;
1566
0
      for (size_t j = 0; j < mlp->nitems; ++j)
1567
0
        if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1568
0
          {
1569
0
            header = mlp->item[j]->msgstr;
1570
0
            break;
1571
0
          }
1572
1573
      /* Extract the charset name.  */
1574
0
      const char *charset = "ASCII";
1575
0
      char *allocated_charset = NULL;
1576
0
      if (header != NULL)
1577
0
        {
1578
0
          const char *charsetstr = c_strstr (header, "charset=");
1579
1580
0
          if (charsetstr != NULL)
1581
0
            {
1582
0
              charsetstr += strlen ("charset=");
1583
0
              size_t len = strcspn (charsetstr, " \t\n");
1584
1585
0
              allocated_charset = (char *) xmalloca (len + 1);
1586
0
              memcpy (allocated_charset, charsetstr, len);
1587
0
              allocated_charset[len] = '\0';
1588
1589
0
              charset = allocated_charset;
1590
1591
              /* Treat the dummy default value as if it were absent.  */
1592
0
              if (strcmp (charset, "CHARSET") == 0)
1593
0
                charset = "ASCII";
1594
0
            }
1595
0
        }
1596
1597
      /* Write out each of the messages for this domain.  */
1598
0
      for (size_t j = 0; j < mlp->nitems; ++j)
1599
0
        if (!mlp->item[j]->obsolete)
1600
0
          {
1601
0
            message_print (mlp->item[j], stream, charset, page_width,
1602
0
                           blank_line, xeh, debug);
1603
0
            blank_line = true;
1604
0
          }
1605
1606
      /* Write out each of the obsolete messages for this domain.  */
1607
0
      for (size_t j = 0; j < mlp->nitems; ++j)
1608
0
        if (mlp->item[j]->obsolete)
1609
0
          {
1610
0
            message_print_obsolete (mlp->item[j], stream, charset, page_width,
1611
0
                                    blank_line, xeh, debug);
1612
0
            blank_line = true;
1613
0
          }
1614
1615
0
      if (allocated_charset != NULL)
1616
0
        freea (allocated_charset);
1617
0
    }
1618
0
}
1619
1620
1621
/* Describes a PO file in .po syntax.  */
1622
const struct catalog_output_format output_format_po =
1623
{
1624
  msgdomain_list_print_po,              /* print */
1625
  false,                                /* requires_utf8 */
1626
  true,                                 /* requires_utf8_for_filenames_with_spaces */
1627
  true,                                 /* supports_color */
1628
  true,                                 /* supports_multiple_domains */
1629
  true,                                 /* supports_contexts */
1630
  true,                                 /* supports_plurals */
1631
  true,                                 /* sorts_obsoletes_to_end */
1632
  false,                                /* alternative_is_po */
1633
  false                                 /* alternative_is_java_class */
1634
};