Coverage Report

Created: 2026-01-25 07:18

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