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