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