Coverage Report

Created: 2026-03-12 07:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gettext/gettext-tools/libgettextpo/gettext-po.c
Line
Count
Source
1
/* Public API for GNU gettext PO files.
2
   Copyright (C) 2003-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 Bruno Haible.  */
18
19
#include <config.h>
20
21
/* Specification.  */
22
#include "gettext-po.h"
23
24
#include <limits.h>
25
#include <stdio.h>
26
#include <stdlib.h>
27
#include <string.h>
28
29
#include "message.h"
30
#include "str-list.h"
31
#include "xalloc.h"
32
#include "read-catalog.h"
33
#include "read-po.h"
34
#include "write-catalog.h"
35
#include "write-po.h"
36
#include "xvasprintf.h"
37
#include "msgl-check.h"
38
#include "glthread/once.h"
39
#include "gettext.h"
40
41
0
#define _(str) gettext(str)
42
43
44
struct po_file
45
{
46
  msgdomain_list_ty *mdlp;
47
  const char *real_filename;
48
  const char *logical_filename;
49
  const char **domains;
50
  /* Heap allocations that are guaranteed to persist until the po_file_free
51
     call.  */
52
  string_list_ty arena;
53
};
54
55
struct po_message_iterator
56
{
57
  po_file_t file;
58
  char *domain;
59
  message_list_ty *mlp;
60
  size_t index;
61
};
62
63
/* A po_message_t is actually a 'struct message_ty *'.  */
64
65
/* A po_filepos_t is actually a 'lex_pos_ty *'.  */
66
67
struct po_flag_iterator
68
{
69
  po_message_t message;
70
  int kind; /* 0: workflow flags, 1: sticky flags */
71
  size_t index;
72
};
73
74
75
/* Version number: (major<<16) + (minor<<8) + subminor */
76
int libgettextpo_version = LIBGETTEXTPO_VERSION;
77
78
79
/* Create an empty PO file representation in memory.  */
80
81
po_file_t
82
po_file_create (void)
83
0
{
84
0
  po_file_t file;
85
86
0
  file = XMALLOC (struct po_file);
87
0
  file->mdlp = msgdomain_list_alloc (false);
88
0
  file->real_filename = _("<unnamed>");
89
0
  file->logical_filename = file->real_filename;
90
0
  file->domains = NULL;
91
0
  string_list_init (&file->arena);
92
0
  return file;
93
0
}
94
95
96
/* Read a PO file into memory.
97
   Return its contents.  Upon failure, return NULL and set errno.  */
98
99
po_file_t
100
po_file_read (const char *filename, po_xerror_handler_t handler)
101
11.9k
{
102
11.9k
  FILE *fp;
103
11.9k
  po_file_t file;
104
105
11.9k
  if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0)
106
0
    {
107
0
      filename = _("<stdin>");
108
0
      fp = stdin;
109
0
    }
110
11.9k
  else
111
11.9k
    {
112
11.9k
      fp = fopen (filename, "r");
113
11.9k
      if (fp == NULL)
114
0
        return NULL;
115
11.9k
    }
116
117
  /* Establish error handler for read_catalog_stream().  */
118
11.9k
  unsigned int error_count = 0;
119
11.9k
  struct xerror_handler local_xerror_handler =
120
11.9k
    {
121
11.9k
      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
122
11.9k
      handler->xerror,
123
11.9k
      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *))
124
11.9k
      handler->xerror2,
125
11.9k
      &error_count
126
11.9k
    };
127
128
11.9k
  file = XMALLOC (struct po_file);
129
11.9k
  file->real_filename = filename;
130
11.9k
  file->logical_filename = filename;
131
11.9k
  string_list_init (&file->arena);
132
11.9k
  file->mdlp = read_catalog_stream (fp, file->real_filename,
133
11.9k
                                    file->logical_filename, &input_format_po,
134
11.9k
                                    &local_xerror_handler, &file->arena);
135
11.9k
  file->domains = NULL;
136
137
11.9k
  if (fp != stdin)
138
11.9k
    fclose (fp);
139
11.9k
  return file;
140
11.9k
}
141
142
143
/* Write an in-memory PO file to a file.
144
   Upon failure, return NULL and set errno.  */
145
146
po_file_t
147
po_file_write (po_file_t file, const char *filename, po_xerror_handler_t handler)
148
0
{
149
  /* Establish error handler for msgdomain_list_print().  */
150
0
  unsigned int error_count = 0;
151
0
  struct xerror_handler local_xerror_handler =
152
0
    {
153
0
      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
154
0
      handler->xerror,
155
0
      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *))
156
0
      handler->xerror2,
157
0
      &error_count
158
0
    };
159
160
0
  msgdomain_list_print (file->mdlp, filename, &output_format_po,
161
0
                        &local_xerror_handler, true, false);
162
163
0
  return file;
164
0
}
165
166
167
/* Free a PO file from memory.  */
168
169
void
170
po_file_free (po_file_t file)
171
11.9k
{
172
11.9k
  msgdomain_list_free (file->mdlp);
173
11.9k
  if (file->domains != NULL)
174
11.9k
    free (file->domains);
175
11.9k
  string_list_destroy (&file->arena);
176
11.9k
  free (file);
177
11.9k
}
178
179
180
/* Return the names of the domains covered by a PO file in memory.  */
181
182
const char * const *
183
po_file_domains (po_file_t file)
184
0
{
185
0
  if (file->domains == NULL)
186
0
    {
187
0
      size_t n = file->mdlp->nitems;
188
0
      const char **domains = XNMALLOC (n + 1, const char *);
189
0
      size_t j;
190
191
0
      for (j = 0; j < n; j++)
192
0
        domains[j] = file->mdlp->item[j]->domain;
193
0
      domains[n] = NULL;
194
195
0
      file->domains = domains;
196
0
    }
197
198
0
  return file->domains;
199
0
}
200
201
202
/* Return the header entry of a domain of a PO file in memory.
203
   The domain NULL denotes the default domain.
204
   Return NULL if there is no header entry.  */
205
206
const char *
207
po_file_domain_header (po_file_t file, const char *domain)
208
0
{
209
0
  message_list_ty *mlp;
210
0
  size_t j;
211
212
0
  if (domain == NULL)
213
0
    domain = MESSAGE_DOMAIN_DEFAULT;
214
0
  mlp = msgdomain_list_sublist (file->mdlp, domain, false);
215
0
  if (mlp != NULL)
216
0
    for (j = 0; j < mlp->nitems; j++)
217
0
      if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
218
0
        {
219
0
          const char *header = mlp->item[j]->msgstr;
220
221
0
          if (header != NULL)
222
0
            return xstrdup (header);
223
0
          else
224
0
            return NULL;
225
0
        }
226
0
  return NULL;
227
0
}
228
229
230
/* Return the value of a field in a header entry.
231
   The return value is either a freshly allocated string, to be freed by the
232
   caller, or NULL.  */
233
234
char *
235
po_header_field (const char *header, const char *field)
236
0
{
237
0
  size_t field_len = strlen (field);
238
0
  const char *line;
239
240
0
  for (line = header;;)
241
0
    {
242
0
      if (strncmp (line, field, field_len) == 0 && line[field_len] == ':')
243
0
        {
244
0
          const char *value_start;
245
0
          const char *value_end;
246
0
          char *value;
247
248
0
          value_start = line + field_len + 1;
249
0
          if (*value_start == ' ')
250
0
            value_start++;
251
0
          value_end = strchr (value_start, '\n');
252
0
          if (value_end == NULL)
253
0
            value_end = value_start + strlen (value_start);
254
255
0
          value = XNMALLOC (value_end - value_start + 1, char);
256
0
          memcpy (value, value_start, value_end - value_start);
257
0
          value[value_end - value_start] = '\0';
258
259
0
          return value;
260
0
        }
261
262
0
      line = strchr (line, '\n');
263
0
      if (line != NULL)
264
0
        line++;
265
0
      else
266
0
        break;
267
0
    }
268
269
0
  return NULL;
270
0
}
271
272
273
/* Return the header entry with a given field set to a given value.  The field
274
   is added if necessary.
275
   The return value is a freshly allocated string.  */
276
277
char *
278
po_header_set_field (const char *header, const char *field, const char *value)
279
0
{
280
0
  size_t header_len = strlen (header);
281
0
  size_t field_len = strlen (field);
282
0
  size_t value_len = strlen (value);
283
284
0
  {
285
0
    const char *line;
286
287
0
    for (line = header;;)
288
0
      {
289
0
        if (strncmp (line, field, field_len) == 0 && line[field_len] == ':')
290
0
          {
291
0
            const char *oldvalue_start;
292
0
            const char *oldvalue_end;
293
0
            size_t header_part1_len;
294
0
            size_t header_part3_len;
295
0
            size_t result_len;
296
0
            char *result;
297
298
0
            oldvalue_start = line + field_len + 1;
299
0
            if (*oldvalue_start == ' ')
300
0
              oldvalue_start++;
301
0
            oldvalue_end = strchr (oldvalue_start, '\n');
302
0
            if (oldvalue_end == NULL)
303
0
              oldvalue_end = oldvalue_start + strlen (oldvalue_start);
304
305
0
            header_part1_len = oldvalue_start - header;
306
0
            header_part3_len = header + header_len - oldvalue_end;
307
0
            result_len = header_part1_len + value_len + header_part3_len;
308
                    /* = header_len - oldvalue_len + value_len */
309
0
            result = XNMALLOC (result_len + 1, char);
310
0
            memcpy (result, header, header_part1_len);
311
0
            memcpy (result + header_part1_len, value, value_len);
312
0
            memcpy (result + header_part1_len + value_len, oldvalue_end,
313
0
                    header_part3_len);
314
0
            *(result + result_len) = '\0';
315
316
0
            return result;
317
0
          }
318
319
0
        line = strchr (line, '\n');
320
0
        if (line != NULL)
321
0
          line++;
322
0
        else
323
0
          break;
324
0
      }
325
0
  }
326
0
  {
327
0
    size_t newline;
328
0
    size_t result_len;
329
0
    char *result;
330
331
0
    newline = (header_len > 0 && header[header_len - 1] != '\n' ? 1 : 0);
332
0
    result_len = header_len + newline + field_len + 2 + value_len + 1;
333
0
    result = XNMALLOC (result_len + 1, char);
334
0
    memcpy (result, header, header_len);
335
0
    if (newline)
336
0
      *(result + header_len) = '\n';
337
0
    memcpy (result + header_len + newline, field, field_len);
338
0
    *(result + header_len + newline + field_len) = ':';
339
0
    *(result + header_len + newline + field_len + 1) = ' ';
340
0
    memcpy (result + header_len + newline + field_len + 2, value, value_len);
341
0
    *(result + header_len + newline + field_len + 2 + value_len) = '\n';
342
0
    *(result + result_len) = '\0';
343
344
0
    return result;
345
0
  }
346
0
}
347
348
349
/* Create an iterator for traversing a domain of a PO file in memory.
350
   The domain NULL denotes the default domain.  */
351
352
po_message_iterator_t
353
po_message_iterator (po_file_t file, const char *domain)
354
0
{
355
0
  po_message_iterator_t iterator;
356
357
0
  if (domain == NULL)
358
0
    domain = MESSAGE_DOMAIN_DEFAULT;
359
360
0
  iterator = XMALLOC (struct po_message_iterator);
361
0
  iterator->file = file;
362
0
  iterator->domain = xstrdup (domain);
363
0
  iterator->mlp = msgdomain_list_sublist (file->mdlp, domain, false);
364
0
  iterator->index = 0;
365
366
0
  return iterator;
367
0
}
368
369
370
/* Free an iterator.  */
371
372
void
373
po_message_iterator_free (po_message_iterator_t iterator)
374
0
{
375
0
  free (iterator->domain);
376
0
  free (iterator);
377
0
}
378
379
380
/* Return the next message, and advance the iterator.
381
   Return NULL at the end of the message list.  */
382
383
po_message_t
384
po_next_message (po_message_iterator_t iterator)
385
0
{
386
0
  if (iterator->mlp != NULL && iterator->index < iterator->mlp->nitems)
387
0
    return (po_message_t) iterator->mlp->item[iterator->index++];
388
0
  else
389
0
    return NULL;
390
0
}
391
392
393
/* Insert a message in a PO file in memory, in the domain and at the position
394
   indicated by the iterator.  The iterator thereby advances past the freshly
395
   inserted message.  */
396
397
void
398
po_message_insert (po_message_iterator_t iterator, po_message_t message)
399
0
{
400
0
  message_ty *mp = (message_ty *) message;
401
402
0
  if (iterator->mlp == NULL)
403
    /* Now we need to allocate a sublist corresponding to the iterator.  */
404
0
    iterator->mlp =
405
0
      msgdomain_list_sublist (iterator->file->mdlp, iterator->domain, true);
406
  /* Insert the message.  */
407
0
  message_list_insert_at (iterator->mlp, iterator->index, mp);
408
  /* Advance the iterator.  */
409
0
  iterator->index++;
410
0
}
411
412
413
/* Return a freshly constructed message.
414
   To finish initializing the message, you must set the msgid and msgstr.  */
415
416
po_message_t
417
po_message_create (void)
418
0
{
419
0
  lex_pos_ty pos = { NULL, 0 };
420
421
0
  return (po_message_t) message_alloc (NULL, NULL, NULL, xstrdup (""), 1, &pos);
422
0
}
423
424
425
/* Return the context of a message, or NULL for a message not restricted to a
426
   context.  */
427
const char *
428
po_message_msgctxt (po_message_t message)
429
0
{
430
0
  message_ty *mp = (message_ty *) message;
431
432
0
  return mp->msgctxt;
433
0
}
434
435
436
/* Change the context of a message. NULL means a message not restricted to a
437
   context.  */
438
void
439
po_message_set_msgctxt (po_message_t message, const char *msgctxt)
440
0
{
441
0
  message_ty *mp = (message_ty *) message;
442
443
0
  if (msgctxt != mp->msgctxt)
444
0
    {
445
0
      char *old_msgctxt = (char *) mp->msgctxt;
446
447
0
      mp->msgctxt = (msgctxt != NULL ? xstrdup (msgctxt) : NULL);
448
0
      if (old_msgctxt != NULL)
449
0
        free (old_msgctxt);
450
0
    }
451
0
}
452
453
454
/* Return the msgid (untranslated English string) of a message.  */
455
456
const char *
457
po_message_msgid (po_message_t message)
458
0
{
459
0
  message_ty *mp = (message_ty *) message;
460
461
0
  return mp->msgid;
462
0
}
463
464
465
/* Change the msgid (untranslated English string) of a message.  */
466
467
void
468
po_message_set_msgid (po_message_t message, const char *msgid)
469
0
{
470
0
  message_ty *mp = (message_ty *) message;
471
472
0
  if (msgid != mp->msgid)
473
0
    {
474
0
      char *old_msgid = (char *) mp->msgid;
475
476
0
      mp->msgid = xstrdup (msgid);
477
0
      if (old_msgid != NULL)
478
0
        free (old_msgid);
479
0
    }
480
0
}
481
482
483
/* Return the msgid_plural (untranslated English plural string) of a message,
484
   or NULL for a message without plural.  */
485
486
const char *
487
po_message_msgid_plural (po_message_t message)
488
0
{
489
0
  message_ty *mp = (message_ty *) message;
490
491
0
  return mp->msgid_plural;
492
0
}
493
494
495
/* Change the msgid_plural (untranslated English plural string) of a message.
496
   NULL means a message without plural.  */
497
498
void
499
po_message_set_msgid_plural (po_message_t message, const char *msgid_plural)
500
0
{
501
0
  message_ty *mp = (message_ty *) message;
502
503
0
  if (msgid_plural != mp->msgid_plural)
504
0
    {
505
0
      char *old_msgid_plural = (char *) mp->msgid_plural;
506
507
0
      mp->msgid_plural = (msgid_plural != NULL ? xstrdup (msgid_plural) : NULL);
508
0
      if (old_msgid_plural != NULL)
509
0
        free (old_msgid_plural);
510
0
    }
511
0
}
512
513
514
/* Return the msgstr (translation) of a message.
515
   Return the empty string for an untranslated message.  */
516
517
const char *
518
po_message_msgstr (po_message_t message)
519
0
{
520
0
  message_ty *mp = (message_ty *) message;
521
522
0
  return mp->msgstr;
523
0
}
524
525
526
/* Change the msgstr (translation) of a message.
527
   Use an empty string to denote an untranslated message.  */
528
529
void
530
po_message_set_msgstr (po_message_t message, const char *msgstr)
531
0
{
532
0
  message_ty *mp = (message_ty *) message;
533
534
0
  if (msgstr != mp->msgstr)
535
0
    {
536
0
      char *old_msgstr = (char *) mp->msgstr;
537
538
0
      mp->msgstr = xstrdup (msgstr);
539
0
      mp->msgstr_len = strlen (mp->msgstr) + 1;
540
0
      if (old_msgstr != NULL)
541
0
        free (old_msgstr);
542
0
    }
543
0
}
544
545
546
/* Return the msgstr[index] for a message with plural handling, or
547
   NULL when the index is out of range or for a message without plural.  */
548
549
const char *
550
po_message_msgstr_plural (po_message_t message, int index)
551
0
{
552
0
  message_ty *mp = (message_ty *) message;
553
554
0
  if (mp->msgid_plural != NULL && index >= 0)
555
0
    {
556
0
      const char *p;
557
0
      const char *p_end = mp->msgstr + mp->msgstr_len;
558
559
0
      for (p = mp->msgstr; ; p += strlen (p) + 1, index--)
560
0
        {
561
0
          if (p >= p_end)
562
0
            return NULL;
563
0
          if (index == 0)
564
0
            break;
565
0
        }
566
0
      return p;
567
0
    }
568
0
  else
569
0
    return NULL;
570
0
}
571
572
573
/* Change the msgstr[index] for a message with plural handling.
574
   Use a NULL value at the end to reduce the number of plural forms.  */
575
576
void
577
po_message_set_msgstr_plural (po_message_t message, int index, const char *msgstr)
578
0
{
579
0
  message_ty *mp = (message_ty *) message;
580
581
0
  if (mp->msgid_plural != NULL && index >= 0)
582
0
    {
583
0
      char *p = (char *) mp->msgstr;
584
0
      char *p_end = (char *) mp->msgstr + mp->msgstr_len;
585
0
      char *copied_msgstr;
586
587
      /* Special care must be taken of the case that msgstr points into the
588
         mp->msgstr string list, because mp->msgstr may be relocated before we
589
         are done with msgstr.  */
590
0
      if (msgstr >= p && msgstr < p_end)
591
0
        msgstr = copied_msgstr = xstrdup (msgstr);
592
0
      else
593
0
        copied_msgstr = NULL;
594
595
0
      for (; ; p += strlen (p) + 1, index--)
596
0
        {
597
0
          if (p >= p_end)
598
0
            {
599
              /* Append at the end.  */
600
0
              if (msgstr != NULL)
601
0
                {
602
0
                  size_t new_msgstr_len = mp->msgstr_len + index + strlen (msgstr) + 1;
603
604
0
                  mp->msgstr =
605
0
                    (char *) xrealloc ((char *) mp->msgstr, new_msgstr_len);
606
0
                  p = (char *) mp->msgstr + mp->msgstr_len;
607
0
                  for (; index > 0; index--)
608
0
                    *p++ = '\0';
609
0
                  memcpy (p, msgstr, strlen (msgstr) + 1);
610
0
                  mp->msgstr_len = new_msgstr_len;
611
0
                }
612
0
              if (copied_msgstr != NULL)
613
0
                free (copied_msgstr);
614
0
              return;
615
0
            }
616
0
          if (index == 0)
617
0
            break;
618
0
        }
619
0
      if (msgstr == NULL)
620
0
        {
621
0
          if (p + strlen (p) + 1 >= p_end)
622
0
            {
623
              /* Remove the string that starts at p.  */
624
0
              mp->msgstr_len = p - mp->msgstr;
625
0
              return;
626
0
            }
627
          /* It is not possible to remove an element of the string list
628
             except the last one.  So just replace it with the empty string.
629
             That's the best we can do here.  */
630
0
          msgstr = "";
631
0
        }
632
0
      {
633
        /* Replace the string that starts at p.  */
634
0
        size_t i1 = p - mp->msgstr;
635
0
        size_t i2before = i1 + strlen (p);
636
0
        size_t i2after = i1 + strlen (msgstr);
637
0
        size_t new_msgstr_len = mp->msgstr_len - i2before + i2after;
638
639
0
        if (i2after > i2before)
640
0
          mp->msgstr = (char *) xrealloc ((char *) mp->msgstr, new_msgstr_len);
641
0
        memmove ((char *) mp->msgstr + i2after, mp->msgstr + i2before,
642
0
                 mp->msgstr_len - i2before);
643
0
        memcpy ((char *) mp->msgstr + i1, msgstr, i2after - i1);
644
0
        mp->msgstr_len = new_msgstr_len;
645
0
      }
646
0
      if (copied_msgstr != NULL)
647
0
        free (copied_msgstr);
648
0
    }
649
0
}
650
651
652
/* Return the comments for a message.  */
653
654
const char *
655
po_message_comments (po_message_t message)
656
0
{
657
  /* FIXME: memory leak.  */
658
0
  message_ty *mp = (message_ty *) message;
659
660
0
  if (mp->comment == NULL || mp->comment->nitems == 0)
661
0
    return "";
662
0
  else
663
0
    return string_list_join (mp->comment, "\n", '\n', true);
664
0
}
665
666
667
/* Change the comments for a message.
668
   comments should be a multiline string, ending in a newline, or empty.  */
669
670
void
671
po_message_set_comments (po_message_t message, const char *comments)
672
0
{
673
0
  message_ty *mp = (message_ty *) message;
674
0
  string_list_ty *slp = string_list_alloc ();
675
676
0
  {
677
0
    char *copy = xstrdup (comments);
678
0
    char *rest;
679
680
0
    rest = copy;
681
0
    while (*rest != '\0')
682
0
      {
683
0
        char *newline = strchr (rest, '\n');
684
685
0
        if (newline != NULL)
686
0
          {
687
0
            *newline = '\0';
688
0
            string_list_append (slp, rest);
689
0
            rest = newline + 1;
690
0
          }
691
0
        else
692
0
          {
693
0
            string_list_append (slp, rest);
694
0
            break;
695
0
          }
696
0
      }
697
0
    free (copy);
698
0
  }
699
700
0
  if (mp->comment != NULL)
701
0
    string_list_free (mp->comment);
702
703
0
  mp->comment = slp;
704
0
}
705
706
707
/* Return the extracted comments for a message.  */
708
709
const char *
710
po_message_extracted_comments (po_message_t message)
711
0
{
712
  /* FIXME: memory leak.  */
713
0
  message_ty *mp = (message_ty *) message;
714
715
0
  if (mp->comment_dot == NULL || mp->comment_dot->nitems == 0)
716
0
    return "";
717
0
  else
718
0
    return string_list_join (mp->comment_dot, "\n", '\n', true);
719
0
}
720
721
722
/* Change the extracted comments for a message.
723
   comments should be a multiline string, ending in a newline, or empty.  */
724
725
void
726
po_message_set_extracted_comments (po_message_t message, const char *comments)
727
0
{
728
0
  message_ty *mp = (message_ty *) message;
729
0
  string_list_ty *slp = string_list_alloc ();
730
731
0
  {
732
0
    char *copy = xstrdup (comments);
733
0
    char *rest;
734
735
0
    rest = copy;
736
0
    while (*rest != '\0')
737
0
      {
738
0
        char *newline = strchr (rest, '\n');
739
740
0
        if (newline != NULL)
741
0
          {
742
0
            *newline = '\0';
743
0
            string_list_append (slp, rest);
744
0
            rest = newline + 1;
745
0
          }
746
0
        else
747
0
          {
748
0
            string_list_append (slp, rest);
749
0
            break;
750
0
          }
751
0
      }
752
0
    free (copy);
753
0
  }
754
755
0
  if (mp->comment_dot != NULL)
756
0
    string_list_free (mp->comment_dot);
757
758
0
  mp->comment_dot = slp;
759
0
}
760
761
762
/* Return the i-th file position for a message, or NULL if i is out of
763
   range.  */
764
765
po_filepos_t
766
po_message_filepos (po_message_t message, int i)
767
0
{
768
0
  message_ty *mp = (message_ty *) message;
769
770
0
  if (i >= 0 && (size_t)i < mp->filepos_count)
771
0
    return (po_filepos_t) &mp->filepos[i];
772
0
  else
773
0
    return NULL;
774
0
}
775
776
777
/* Remove the i-th file position from a message.
778
   The indices of all following file positions for the message are decremented
779
   by one.  */
780
781
void
782
po_message_remove_filepos (po_message_t message, int i)
783
0
{
784
0
  message_ty *mp = (message_ty *) message;
785
786
0
  if (i >= 0)
787
0
    {
788
0
      size_t j = (size_t)i;
789
0
      size_t n = mp->filepos_count;
790
791
0
      if (j < n)
792
0
        {
793
0
          mp->filepos_count = n = n - 1;
794
0
          free ((char *) mp->filepos[j].file_name);
795
0
          for (; j < n; j++)
796
0
            mp->filepos[j] = mp->filepos[j + 1];
797
0
        }
798
0
    }
799
0
}
800
801
802
/* Add a file position to a message, if it is not already present for the
803
   message.
804
   file is the file name.
805
   start_line is the line number where the string starts, or (size_t)(-1) if no
806
   line number is available.  */
807
808
void
809
po_message_add_filepos (po_message_t message, const char *file, size_t start_line)
810
0
{
811
0
  message_ty *mp = (message_ty *) message;
812
813
0
  message_comment_filepos (mp, file, start_line);
814
0
}
815
816
817
/* Return the previous context of a message, or NULL for none.  */
818
819
const char *
820
po_message_prev_msgctxt (po_message_t message)
821
0
{
822
0
  message_ty *mp = (message_ty *) message;
823
824
0
  return mp->prev_msgctxt;
825
0
}
826
827
828
/* Change the previous context of a message.  NULL is allowed.  */
829
830
void
831
po_message_set_prev_msgctxt (po_message_t message, const char *prev_msgctxt)
832
0
{
833
0
  message_ty *mp = (message_ty *) message;
834
835
0
  if (prev_msgctxt != mp->prev_msgctxt)
836
0
    {
837
0
      char *old_prev_msgctxt = (char *) mp->prev_msgctxt;
838
839
0
      mp->prev_msgctxt = (prev_msgctxt != NULL ? xstrdup (prev_msgctxt) : NULL);
840
0
      if (old_prev_msgctxt != NULL)
841
0
        free (old_prev_msgctxt);
842
0
    }
843
0
}
844
845
846
/* Return the previous msgid (untranslated English string) of a message, or
847
   NULL for none.  */
848
849
const char *
850
po_message_prev_msgid (po_message_t message)
851
0
{
852
0
  message_ty *mp = (message_ty *) message;
853
854
0
  return mp->prev_msgid;
855
0
}
856
857
858
/* Change the previous msgid (untranslated English string) of a message.
859
   NULL is allowed.  */
860
861
void
862
po_message_set_prev_msgid (po_message_t message, const char *prev_msgid)
863
0
{
864
0
  message_ty *mp = (message_ty *) message;
865
866
0
  if (prev_msgid != mp->prev_msgid)
867
0
    {
868
0
      char *old_prev_msgid = (char *) mp->prev_msgid;
869
870
0
      mp->prev_msgid = (prev_msgid != NULL ? xstrdup (prev_msgid) : NULL);
871
0
      if (old_prev_msgid != NULL)
872
0
        free (old_prev_msgid);
873
0
    }
874
0
}
875
876
877
/* Return the previous msgid_plural (untranslated English plural string) of a
878
   message, or NULL for none.  */
879
880
const char *
881
po_message_prev_msgid_plural (po_message_t message)
882
0
{
883
0
  message_ty *mp = (message_ty *) message;
884
885
0
  return mp->prev_msgid_plural;
886
0
}
887
888
889
/* Change the previous msgid_plural (untranslated English plural string) of a
890
   message.  NULL is allowed.  */
891
892
void
893
po_message_set_prev_msgid_plural (po_message_t message, const char *prev_msgid_plural)
894
0
{
895
0
  message_ty *mp = (message_ty *) message;
896
897
0
  if (prev_msgid_plural != mp->prev_msgid_plural)
898
0
    {
899
0
      char *old_prev_msgid_plural = (char *) mp->prev_msgid_plural;
900
901
0
      mp->prev_msgid_plural =
902
0
        (prev_msgid_plural != NULL ? xstrdup (prev_msgid_plural) : NULL);
903
0
      if (old_prev_msgid_plural != NULL)
904
0
        free (old_prev_msgid_plural);
905
0
    }
906
0
}
907
908
909
/* Return true if the message is marked obsolete.  */
910
911
int
912
po_message_is_obsolete (po_message_t message)
913
0
{
914
0
  message_ty *mp = (message_ty *) message;
915
916
0
  return (mp->obsolete ? 1 : 0);
917
0
}
918
919
920
/* Change the obsolete mark of a message.  */
921
922
void
923
po_message_set_obsolete (po_message_t message, int obsolete)
924
0
{
925
0
  message_ty *mp = (message_ty *) message;
926
927
0
  mp->obsolete = obsolete;
928
0
}
929
930
931
/* Return true if the message is marked fuzzy.  */
932
933
int
934
po_message_is_fuzzy (po_message_t message)
935
0
{
936
0
  message_ty *mp = (message_ty *) message;
937
938
0
  return (mp->is_fuzzy ? 1 : 0);
939
0
}
940
941
942
/* Change the fuzzy mark of a message.  */
943
944
void
945
po_message_set_fuzzy (po_message_t message, int fuzzy)
946
0
{
947
0
  message_ty *mp = (message_ty *) message;
948
949
0
  mp->is_fuzzy = fuzzy;
950
0
}
951
952
953
/* Return true if the message has a given workflow flag.
954
   This function is a generalization of po_message_is_fuzzy.  */
955
956
int
957
po_message_has_workflow_flag (po_message_t message, const char *workflow_flag)
958
0
{
959
0
  if (strcmp (workflow_flag, "fuzzy") == 0)
960
0
    return po_message_is_fuzzy (message);
961
0
  return 0;
962
0
}
963
964
965
/* Set or unset a given workflow flag on a message.
966
   This function is a generalization of po_message_set_fuzzy.    */
967
968
void
969
po_message_set_workflow_flag (po_message_t message, const char *workflow_flag, int value)
970
0
{
971
0
  if (strcmp (workflow_flag, "fuzzy") == 0)
972
0
    po_message_set_fuzzy (message, value);
973
0
}
974
975
976
/* Create an iterator for traversing the list of workflow flags of a message.
977
   This includes the "fuzzy" flag.  */
978
979
po_flag_iterator_t
980
po_message_workflow_flags_iterator (po_message_t message)
981
0
{
982
0
  po_flag_iterator_t iterator = XMALLOC (struct po_flag_iterator);
983
0
  iterator->message = message;
984
0
  iterator->kind = 0;
985
0
  iterator->index = 0;
986
987
0
  return iterator;
988
0
}
989
990
991
/* Return true if the message is marked as being a format string of the given
992
   type (e.g. "c-format").  */
993
994
int
995
po_message_is_format (po_message_t message, const char *format_type)
996
0
{
997
0
  message_ty *mp = (message_ty *) message;
998
0
  size_t len = strlen (format_type);
999
0
  size_t i;
1000
1001
0
  if (len >= 7 && memcmp (format_type + len - 7, "-format", 7) == 0)
1002
0
    for (i = 0; i < NFORMATS; i++)
1003
0
      if (strlen (format_language[i]) == len - 7
1004
0
          && memcmp (format_language[i], format_type, len - 7) == 0)
1005
        /* The given format_type corresponds to (enum format_type) i.  */
1006
        /* See make_format_description_string in write-po.c.  */
1007
0
        return (possible_format_p (mp->is_format[i]) ? 1 : 0);
1008
0
  return 0;
1009
0
}
1010
1011
1012
/* Return the format string mark for a given type (e.g. "c-format") of a
1013
   message.  Returns 1 if the the mark is set, 0 if the opposite mark ("no-*")
1014
   is set, -1 if neither the mark nor the opposite mark is set.  */
1015
1016
int
1017
po_message_get_format (po_message_t message, const char *format_type)
1018
0
{
1019
0
  message_ty *mp = (message_ty *) message;
1020
0
  size_t len = strlen (format_type);
1021
0
  size_t i;
1022
1023
0
  if (len >= 7 && memcmp (format_type + len - 7, "-format", 7) == 0)
1024
0
    for (i = 0; i < NFORMATS; i++)
1025
0
      if (strlen (format_language[i]) == len - 7
1026
0
          && memcmp (format_language[i], format_type, len - 7) == 0)
1027
        /* The given format_type corresponds to (enum format_type) i.  */
1028
0
        {
1029
0
          enum is_format is_format = mp->is_format[i];
1030
          /* See significant_format_p and make_format_description_string
1031
             in write-po.c.  */
1032
0
          return (possible_format_p (is_format) ? 1 :
1033
0
                  not_format_p (is_format) ? 0 :
1034
0
                  -1);
1035
0
        }
1036
0
  return -1;
1037
0
}
1038
1039
1040
/* Change the format string mark for a given type of a message.  */
1041
1042
void
1043
po_message_set_format (po_message_t message, const char *format_type, int value)
1044
0
{
1045
0
  message_ty *mp = (message_ty *) message;
1046
0
  size_t len = strlen (format_type);
1047
0
  size_t i;
1048
1049
0
  if (len >= 7 && memcmp (format_type + len - 7, "-format", 7) == 0)
1050
0
    for (i = 0; i < NFORMATS; i++)
1051
0
      if (strlen (format_language[i]) == len - 7
1052
0
          && memcmp (format_language[i], format_type, len - 7) == 0)
1053
        /* The given format_type corresponds to (enum format_type) i.  */
1054
0
        mp->is_format[i] = (value >= 0 ? (value ? yes : no) : undecided);
1055
0
}
1056
1057
1058
/* Return true if the message has a given sticky flag.
1059
   This function is a generalization of po_message_is_format and
1060
   po_message_get_format.  */
1061
1062
int
1063
po_message_has_sticky_flag (po_message_t message, const char *sticky_flag)
1064
0
{
1065
0
  message_ty *mp = (message_ty *) message;
1066
0
  size_t len = strlen (sticky_flag);
1067
1068
0
  if (len >= 7 && memcmp (sticky_flag + len - 7, "-format", 7) == 0)
1069
0
    {
1070
0
      if (len >= 3 + 7 && memcmp (sticky_flag, "no-", 3) == 0)
1071
0
        {
1072
0
          size_t i;
1073
0
          for (i = 0; i < NFORMATS; i++)
1074
0
            if (strlen (format_language[i]) == len - 3 - 7
1075
0
                && memcmp (format_language[i], sticky_flag + 3, len - 3 - 7) == 0)
1076
              /* The given sticky_flag corresponds to the opposite of
1077
                 (enum format_type) i.  */
1078
0
              return not_format_p (mp->is_format[i]);
1079
0
        }
1080
0
      else
1081
0
        {
1082
0
          size_t i;
1083
0
          for (i = 0; i < NFORMATS; i++)
1084
0
            if (strlen (format_language[i]) == len - 7
1085
0
                && memcmp (format_language[i], sticky_flag, len - 7) == 0)
1086
              /* The given sticky_flag corresponds to (enum format_type) i.  */
1087
0
              return possible_format_p (mp->is_format[i]);
1088
0
        }
1089
0
    }
1090
0
  else if (strcmp (sticky_flag, "no-wrap") == 0)
1091
0
    return mp->do_wrap == no;
1092
0
  return 0;
1093
0
}
1094
1095
1096
/* Set or unset a given sticky flag on a message.
1097
   This function is a generalization of po_message_set_format.  */
1098
1099
void
1100
po_message_set_sticky_flag (po_message_t message, const char *sticky_flag, int value)
1101
0
{
1102
0
  message_ty *mp = (message_ty *) message;
1103
0
  size_t len = strlen (sticky_flag);
1104
1105
0
  if (len >= 7 && memcmp (sticky_flag + len - 7, "-format", 7) == 0)
1106
0
    {
1107
0
      if (len >= 3 + 7 && memcmp (sticky_flag, "no-", 3) == 0)
1108
0
        {
1109
0
          size_t i;
1110
0
          for (i = 0; i < NFORMATS; i++)
1111
0
            if (strlen (format_language[i]) == len - 3 - 7
1112
0
                && memcmp (format_language[i], sticky_flag + 3, len - 3 - 7) == 0)
1113
              /* The given sticky_flag corresponds to the opposite of
1114
                 (enum format_type) i.  */
1115
0
              {
1116
0
                if (value)
1117
0
                  mp->is_format[i] = no;
1118
0
                else
1119
0
                  if (!possible_format_p (mp->is_format[i]))
1120
0
                    mp->is_format[i] = undecided;
1121
0
              }
1122
0
        }
1123
0
      else
1124
0
        {
1125
0
          size_t i;
1126
0
          for (i = 0; i < NFORMATS; i++)
1127
0
            if (strlen (format_language[i]) == len - 7
1128
0
                && memcmp (format_language[i], sticky_flag, len - 7) == 0)
1129
              /* The given sticky_flag corresponds to (enum format_type) i.  */
1130
0
              {
1131
0
                if (value)
1132
0
                  mp->is_format[i] = yes;
1133
0
                else
1134
0
                  if (!not_format_p (mp->is_format[i]))
1135
0
                    mp->is_format[i] = undecided;
1136
0
              }
1137
0
        }
1138
0
    }
1139
0
  else if (strcmp (sticky_flag, "no-wrap") == 0)
1140
0
    mp->do_wrap = (value ? no : yes);
1141
0
}
1142
1143
1144
/* Create an iterator for traversing the list of sticky flags of a message.
1145
   This includes the "*-format" and "no-*-format" flags, as well as the
1146
   "no-wrap" flag.
1147
   It does *not* include the "range", because that is not a flag.  */
1148
1149
po_flag_iterator_t
1150
po_message_sticky_flags_iterator (po_message_t message)
1151
0
{
1152
0
  po_flag_iterator_t iterator = XMALLOC (struct po_flag_iterator);
1153
0
  iterator->message = message;
1154
0
  iterator->kind = 1;
1155
0
  iterator->index = 0;
1156
1157
0
  return iterator;
1158
0
}
1159
1160
1161
/* If a numeric range of a message is set, return true and store the minimum
1162
   and maximum value in *MINP and *MAXP.  */
1163
1164
int
1165
po_message_is_range (po_message_t message, int *minp, int *maxp)
1166
0
{
1167
0
  message_ty *mp = (message_ty *) message;
1168
1169
0
  if (has_range_p (mp->range))
1170
0
    {
1171
0
      *minp = mp->range.min;
1172
0
      *maxp = mp->range.max;
1173
0
      return 1;
1174
0
    }
1175
0
  else
1176
0
    return 0;
1177
0
}
1178
1179
1180
/* Change the numeric range of a message.  MIN and MAX must be non-negative,
1181
   with MIN < MAX.  Use MIN = MAX = -1 to remove the numeric range of a
1182
   message.  */
1183
1184
void
1185
po_message_set_range (po_message_t message, int min, int max)
1186
0
{
1187
0
  message_ty *mp = (message_ty *) message;
1188
1189
0
  if (min >= 0 && max >= min)
1190
0
    {
1191
0
      mp->range.min = min;
1192
0
      mp->range.max = max;
1193
0
    }
1194
0
  else if (min < 0 && max < 0)
1195
0
    {
1196
0
      mp->range.min = -1;
1197
0
      mp->range.max = -1;
1198
0
    }
1199
  /* Other values of min and max are invalid.  */
1200
0
}
1201
1202
1203
/* Return the file name.  */
1204
1205
const char *
1206
po_filepos_file (po_filepos_t filepos)
1207
0
{
1208
0
  lex_pos_ty *pp = (lex_pos_ty *) filepos;
1209
1210
0
  return pp->file_name;
1211
0
}
1212
1213
1214
/* Return the line number where the string starts, or (size_t)(-1) if no line
1215
   number is available.  */
1216
1217
size_t
1218
po_filepos_start_line (po_filepos_t filepos)
1219
0
{
1220
0
  lex_pos_ty *pp = (lex_pos_ty *) filepos;
1221
1222
0
  return pp->line_number;
1223
0
}
1224
1225
1226
/* A NULL terminated array of the supported format types.  */
1227
static const char * const * all_formats;
1228
1229
static void
1230
all_formats_init (void)
1231
0
{
1232
0
  const char **list = XNMALLOC (NFORMATS + 1, const char *);
1233
0
  size_t i;
1234
0
  for (i = 0; i < NFORMATS; i++)
1235
0
    list[i] = xasprintf ("%s-format", format_language[i]);
1236
0
  list[i] = NULL;
1237
0
  all_formats = list;
1238
0
}
1239
1240
/* Ensure that all_formats_init is called once only.  */
1241
gl_once_define(static, all_formats_init_once)
1242
1243
/* Return a NULL terminated array of the supported format types.  */
1244
1245
const char * const *
1246
po_format_list (void)
1247
0
{
1248
0
  gl_once (all_formats_init_once, all_formats_init);
1249
0
  return all_formats;
1250
0
}
1251
1252
1253
/* Return the pretty name associated with a format type.
1254
   For example, for "csharp-format", return "C#".
1255
   Return NULL if the argument is not a supported format type.  */
1256
1257
const char *
1258
po_format_pretty_name (const char *format_type)
1259
0
{
1260
0
  size_t len = strlen (format_type);
1261
0
  size_t i;
1262
1263
0
  if (len >= 7 && memcmp (format_type + len - 7, "-format", 7) == 0)
1264
0
    for (i = 0; i < NFORMATS; i++)
1265
0
      if (strlen (format_language[i]) == len - 7
1266
0
          && memcmp (format_language[i], format_type, len - 7) == 0)
1267
        /* The given format_type corresponds to (enum format_type) i.  */
1268
0
        return format_language_pretty[i];
1269
0
  return NULL;
1270
0
}
1271
1272
1273
/* Free an iterator.  */
1274
1275
void
1276
po_flag_iterator_free (po_flag_iterator_t iterator)
1277
0
{
1278
0
  free (iterator);
1279
0
}
1280
1281
1282
/* Return the next flag, and advance the iterator.
1283
   Return NULL at the end of the list of flags.  */
1284
const char *
1285
po_flag_next (po_flag_iterator_t iterator)
1286
0
{
1287
0
  message_ty *mp = (message_ty *) iterator->message;
1288
1289
0
  if (mp != NULL)
1290
0
    switch (iterator->kind)
1291
0
      {
1292
0
      case 0:
1293
        /* Return the next workflow flag.  */
1294
0
        if (iterator->index == 0)
1295
0
          {
1296
0
            iterator->index++;
1297
0
            if (mp->is_fuzzy)
1298
0
              return "fuzzy";
1299
0
          }
1300
        /* Here iterator->index == 1.  */
1301
0
        return NULL;
1302
1303
0
      case 1:
1304
        /* Return the next sticky flag.  */
1305
0
        while (iterator->index < 2 * NFORMATS + 1)
1306
0
          {
1307
0
            size_t i = iterator->index;
1308
0
            iterator->index++;
1309
0
            if (i < 2 * NFORMATS)
1310
0
              {
1311
0
                if ((i % 2) == 0)
1312
0
                  {
1313
0
                    i = i / 2;
1314
0
                    if (possible_format_p (mp->is_format[i]))
1315
0
                      return format_flag[i] + 3; /* without "no-" prefix */
1316
0
                  }
1317
0
                else
1318
0
                  {
1319
0
                    i = i / 2;
1320
0
                    if (not_format_p (mp->is_format[i]))
1321
0
                      return format_flag[i] + 0; /* with "no-" prefix */
1322
0
                  }
1323
0
              }
1324
0
            else if (i == 2 * NFORMATS)
1325
0
              {
1326
0
                if (mp->do_wrap == no)
1327
0
                  return "no-wrap";
1328
0
              }
1329
0
          }
1330
        /* Here iterator->index == 2 * NFORMATS + 1.  */
1331
0
        return NULL;
1332
1333
0
      default:
1334
0
        abort ();
1335
0
      }
1336
0
  return NULL;
1337
0
}
1338
1339
1340
/* Test whether an entire file PO file is valid, like msgfmt does it.
1341
   If it is invalid, pass the reasons to the handler.  */
1342
1343
void
1344
po_file_check_all (po_file_t file, po_xerror_handler_t handler)
1345
0
{
1346
0
  msgdomain_list_ty *mdlp;
1347
0
  size_t k;
1348
1349
  /* Establish error handler for check_message_list().  */
1350
0
  unsigned int error_count = 0;
1351
0
  struct xerror_handler local_xerror_handler =
1352
0
    {
1353
0
      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
1354
0
      handler->xerror,
1355
0
      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *))
1356
0
      handler->xerror2,
1357
0
      &error_count
1358
0
    };
1359
1360
0
  mdlp = file->mdlp;
1361
0
  for (k = 0; k < mdlp->nitems; k++)
1362
0
    check_message_list (mdlp->item[k]->messages, 1, 1, 1, 1, 1, 0, 0, 0,
1363
0
                        &local_xerror_handler);
1364
0
}
1365
1366
1367
/* Test a single message, to be inserted in a PO file in memory, like msgfmt
1368
   does it.  If it is invalid, pass the reasons to the handler.  The iterator
1369
   is not modified by this call; it only specifies the file and the domain.  */
1370
1371
void
1372
po_message_check_all (po_message_t message, po_message_iterator_t iterator,
1373
                      po_xerror_handler_t handler)
1374
0
{
1375
0
  message_ty *mp = (message_ty *) message;
1376
1377
  /* Establish error handler for check_message_list().  */
1378
0
  unsigned int error_count = 0;
1379
0
  struct xerror_handler local_xerror_handler =
1380
0
    {
1381
0
      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
1382
0
      handler->xerror,
1383
0
      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *))
1384
0
      handler->xerror2,
1385
0
      &error_count
1386
0
    };
1387
1388
  /* For plural checking, combine the message and its header into a small,
1389
     two-element message list.  */
1390
0
  {
1391
0
    message_ty *header;
1392
1393
    /* Find the header.  */
1394
0
    {
1395
0
      message_list_ty *mlp;
1396
0
      size_t j;
1397
1398
0
      header = NULL;
1399
0
      mlp =
1400
0
        msgdomain_list_sublist (iterator->file->mdlp, iterator->domain, false);
1401
0
      if (mlp != NULL)
1402
0
        for (j = 0; j < mlp->nitems; j++)
1403
0
          if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
1404
0
            {
1405
0
              header = mlp->item[j];
1406
0
              break;
1407
0
            }
1408
0
    }
1409
1410
0
    {
1411
0
      message_ty *items[2];
1412
0
      struct message_list_ty ml;
1413
0
      ml.item = items;
1414
0
      ml.nitems = 0;
1415
0
      ml.nitems_max = 2;
1416
0
      ml.use_hashtable = false;
1417
1418
0
      if (header != NULL)
1419
0
        message_list_append (&ml, header);
1420
0
      if (mp != header)
1421
0
        message_list_append (&ml, mp);
1422
1423
0
      check_message_list (&ml, 1, 1, 1, 1, 1, 0, 0, 0, &local_xerror_handler);
1424
0
    }
1425
0
  }
1426
0
}
1427
1428
1429
/* Test whether the message translation is a valid format string if the message
1430
   is marked as being a format string.  If it is invalid, pass the reasons to
1431
   the handler.  */
1432
void
1433
po_message_check_format (po_message_t message, po_xerror_handler_t handler)
1434
0
{
1435
0
  message_ty *mp = (message_ty *) message;
1436
1437
  /* Establish error handler for check_message().  */
1438
0
  unsigned int error_count = 0;
1439
0
  struct xerror_handler local_xerror_handler =
1440
0
    {
1441
0
      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
1442
0
      handler->xerror,
1443
0
      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *, const message_ty *, const char *, size_t, size_t, int, const char *))
1444
0
      handler->xerror2,
1445
0
      &error_count
1446
0
    };
1447
1448
0
  if (!mp->obsolete)
1449
0
    check_message (mp, &mp->pos, 0, 1, NULL, 0, 0, 0, 0, &local_xerror_handler);
1450
0
}