• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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