xref: /netbsd-src/external/gpl2/gettext/dist/gettext-tools/libgettextpo/gettext-po.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
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