Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / libgettextpo / gettext-po.c
1 /* Public API for GNU gettext PO files.
2    Copyright (C) 2003-2010, 2015 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 <http://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
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
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
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
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
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
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
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 *
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 *
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 *
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 *
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
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
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
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
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
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 *
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
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 *
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
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 *
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
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 *
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
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 *
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
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 *
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
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 *
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
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
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
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
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 *
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
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 *
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
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 *
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
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
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
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
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
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
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
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
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
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 *
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
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 *
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 *
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
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
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
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
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
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 }