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