1/* Public API for GNU gettext PO files.
2   Copyright (C) 2003-2005 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
18
19#ifdef HAVE_CONFIG_H
20# include <config.h>
21#endif
22
23/* Specification.  */
24#include "gettext-po.h"
25
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-po.h"
35#include "write-po.h"
36#include "error.h"
37#include "xerror.h"
38#include "po-error.h"
39#include "vasprintf.h"
40#include "format.h"
41#include "gettext.h"
42
43#define _(str) gettext(str)
44
45
46struct po_file
47{
48  msgdomain_list_ty *mdlp;
49  const char *real_filename;
50  const char *logical_filename;
51  const char **domains;
52};
53
54struct po_message_iterator
55{
56  po_file_t file;
57  char *domain;
58  message_list_ty *mlp;
59  size_t index;
60};
61
62/* A po_message_t is actually a 'struct message_ty *'.  */
63
64/* A po_filepos_t is actually a 'lex_pos_ty *'.  */
65
66
67/* Version number: (major<<16) + (minor<<8) + subminor */
68int libgettextpo_version = LIBGETTEXTPO_VERSION;
69
70
71/* Create an empty PO file representation in memory.  */
72
73po_file_t
74po_file_create (void)
75{
76  po_file_t file;
77
78  file = (struct po_file *) xmalloc (sizeof (struct po_file));
79  file->mdlp = msgdomain_list_alloc (false);
80  file->real_filename = _("<unnamed>");
81  file->logical_filename = file->real_filename;
82  file->domains = NULL;
83  return file;
84}
85
86
87/* Read a PO file into memory.
88   Return its contents.  Upon failure, return NULL and set errno.  */
89
90po_file_t
91po_file_read (const char *filename, po_error_handler_t handler)
92{
93  FILE *fp;
94  po_file_t file;
95
96  if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0)
97    {
98      filename = _("<stdin>");
99      fp = stdin;
100    }
101  else
102    {
103      fp = fopen (filename, "r");
104      if (fp == NULL)
105	return NULL;
106    }
107
108  /* Establish error handler around read_po().  */
109  po_error             = handler->error;
110  po_error_at_line     = handler->error_at_line;
111  po_multiline_warning = handler->multiline_warning;
112  po_multiline_error   = handler->multiline_error;
113
114  file = (struct po_file *) xmalloc (sizeof (struct po_file));
115  file->real_filename = filename;
116  file->logical_filename = filename;
117  file->mdlp = read_po (fp, file->real_filename, file->logical_filename);
118  file->domains = NULL;
119
120  /* Restore error handler.  */
121  po_error             = error;
122  po_error_at_line     = error_at_line;
123  po_multiline_warning = multiline_warning;
124  po_multiline_error   = multiline_error;
125
126  if (fp != stdin)
127    fclose (fp);
128  return file;
129}
130#undef po_file_read
131
132/* Older version for binary backward compatibility.  */
133po_file_t
134po_file_read (const char *filename)
135{
136  FILE *fp;
137  po_file_t file;
138
139  if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0)
140    {
141      filename = _("<stdin>");
142      fp = stdin;
143    }
144  else
145    {
146      fp = fopen (filename, "r");
147      if (fp == NULL)
148	return NULL;
149    }
150
151  file = (struct po_file *) xmalloc (sizeof (struct po_file));
152  file->real_filename = filename;
153  file->logical_filename = filename;
154  file->mdlp = read_po (fp, file->real_filename, file->logical_filename);
155  file->domains = NULL;
156
157  if (fp != stdin)
158    fclose (fp);
159  return file;
160}
161
162
163/* Write an in-memory PO file to a file.
164   Upon failure, return NULL and set errno.  */
165
166po_file_t
167po_file_write (po_file_t file, const char *filename, po_error_handler_t handler)
168{
169  /* Establish error handler around msgdomain_list_print().  */
170  po_error             = handler->error;
171  po_error_at_line     = handler->error_at_line;
172  po_multiline_warning = handler->multiline_warning;
173  po_multiline_error   = handler->multiline_error;
174
175  msgdomain_list_print (file->mdlp, filename, true, false);
176
177  /* Restore error handler.  */
178  po_error             = error;
179  po_error_at_line     = error_at_line;
180  po_multiline_warning = multiline_warning;
181  po_multiline_error   = multiline_error;
182
183  return file;
184}
185
186
187/* Free a PO file from memory.  */
188
189void
190po_file_free (po_file_t file)
191{
192  msgdomain_list_free (file->mdlp);
193  if (file->domains != NULL)
194    free (file->domains);
195  free (file);
196}
197
198
199/* Return the names of the domains covered by a PO file in memory.  */
200
201const char * const *
202po_file_domains (po_file_t file)
203{
204  if (file->domains == NULL)
205    {
206      size_t n = file->mdlp->nitems;
207      const char **domains =
208	(const char **) xmalloc ((n + 1) * sizeof (const char *));
209      size_t j;
210
211      for (j = 0; j < n; j++)
212	domains[j] = file->mdlp->item[j]->domain;
213      domains[n] = NULL;
214
215      file->domains = domains;
216    }
217
218  return file->domains;
219}
220
221
222/* Return the header entry of a domain of a PO file in memory.
223   The domain NULL denotes the default domain.
224   Return NULL if there is no header entry.  */
225
226const char *
227po_file_domain_header (po_file_t file, const char *domain)
228{
229  message_list_ty *mlp;
230  size_t j;
231
232  if (domain == NULL)
233    domain = MESSAGE_DOMAIN_DEFAULT;
234  mlp = msgdomain_list_sublist (file->mdlp, domain, false);
235  if (mlp != NULL)
236    for (j = 0; j < mlp->nitems; j++)
237      if (mlp->item[j]->msgid[0] == '\0' && !mlp->item[j]->obsolete)
238	{
239	  const char *header = mlp->item[j]->msgstr;
240
241	  if (header != NULL)
242	    return xstrdup (header);
243	  else
244	    return NULL;
245	}
246  return NULL;
247}
248
249
250/* Return the value of a field in a header entry.
251   The return value is either a freshly allocated string, to be freed by the
252   caller, or NULL.  */
253
254char *
255po_header_field (const char *header, const char *field)
256{
257  size_t field_len = strlen (field);
258  const char *line;
259
260  for (line = header;;)
261    {
262      if (strncmp (line, field, field_len) == 0
263	  && line[field_len] == ':' && line[field_len + 1] == ' ')
264	{
265	  const char *value_start;
266	  const char *value_end;
267	  char *value;
268
269	  value_start = line + field_len + 2;
270	  value_end = strchr (value_start, '\n');
271	  if (value_end == NULL)
272	    value_end = value_start + strlen (value_start);
273
274	  value = (char *) xmalloc (value_end - value_start + 1);
275	  memcpy (value, value_start, value_end - value_start);
276	  value[value_end - value_start] = '\0';
277
278	  return value;
279	}
280
281      line = strchr (line, '\n');
282      if (line != NULL)
283	line++;
284      else
285	break;
286    }
287
288  return NULL;
289}
290
291
292/* Return the header entry with a given field set to a given value.  The field
293   is added if necessary.
294   The return value is a freshly allocated string.  */
295
296char *
297po_header_set_field (const char *header, const char *field, const char *value)
298{
299  size_t header_len = strlen (header);
300  size_t field_len = strlen (field);
301  size_t value_len = strlen (value);
302
303  {
304    const char *line;
305
306    for (line = header;;)
307      {
308	if (strncmp (line, field, field_len) == 0
309	    && line[field_len] == ':' && line[field_len + 1] == ' ')
310	  {
311	    const char *oldvalue_start;
312	    const char *oldvalue_end;
313	    size_t oldvalue_len;
314	    size_t header_part1_len;
315	    size_t header_part3_len;
316	    size_t result_len;
317	    char *result;
318
319	    oldvalue_start = line + field_len + 2;
320	    oldvalue_end = strchr (oldvalue_start, '\n');
321	    if (oldvalue_end == NULL)
322	      oldvalue_end = oldvalue_start + strlen (oldvalue_start);
323	    oldvalue_len = oldvalue_end - oldvalue_start;
324
325	    header_part1_len = oldvalue_start - header;
326	    header_part3_len = header + header_len - oldvalue_end;
327	    result_len = header_part1_len + value_len + header_part3_len;
328		    /* = header_len - oldvalue_len + value_len */
329	    result = (char *) xmalloc (result_len + 1);
330	    memcpy (result, header, header_part1_len);
331	    memcpy (result + header_part1_len, value, value_len);
332	    memcpy (result + header_part1_len + value_len, oldvalue_end,
333		    header_part3_len);
334	    *(result + result_len) = '\0';
335
336	    return result;
337	  }
338
339	line = strchr (line, '\n');
340	if (line != NULL)
341	  line++;
342	else
343	  break;
344      }
345  }
346  {
347    size_t newline;
348    size_t result_len;
349    char *result;
350
351    newline = (header_len > 0 && header[header_len - 1] != '\n' ? 1 : 0);
352    result_len = header_len + newline + field_len + 2 + value_len + 1;
353    result = (char *) xmalloc (result_len + 1);
354    memcpy (result, header, header_len);
355    if (newline)
356      *(result + header_len) = '\n';
357    memcpy (result + header_len + newline, field, field_len);
358    *(result + header_len + newline + field_len) = ':';
359    *(result + header_len + newline + field_len + 1) = ' ';
360    memcpy (result + header_len + newline + field_len + 2, value, value_len);
361    *(result + header_len + newline + field_len + 2 + value_len) = '\n';
362    *(result + result_len) = '\0';
363
364    return result;
365  }
366}
367
368
369/* Create an iterator for traversing a domain of a PO file in memory.
370   The domain NULL denotes the default domain.  */
371
372po_message_iterator_t
373po_message_iterator (po_file_t file, const char *domain)
374{
375  po_message_iterator_t iterator;
376
377  if (domain == NULL)
378    domain = MESSAGE_DOMAIN_DEFAULT;
379
380  iterator =
381    (struct po_message_iterator *)
382    xmalloc (sizeof (struct po_message_iterator));
383  iterator->file = file;
384  iterator->domain = xstrdup (domain);
385  iterator->mlp = msgdomain_list_sublist (file->mdlp, domain, false);
386  iterator->index = 0;
387
388  return iterator;
389}
390
391
392/* Free an iterator.  */
393
394void
395po_message_iterator_free (po_message_iterator_t iterator)
396{
397  free (iterator->domain);
398  free (iterator);
399}
400
401
402/* Return the next message, and advance the iterator.
403   Return NULL at the end of the message list.  */
404
405po_message_t
406po_next_message (po_message_iterator_t iterator)
407{
408  if (iterator->mlp != NULL && iterator->index < iterator->mlp->nitems)
409    return (po_message_t) iterator->mlp->item[iterator->index++];
410  else
411    return NULL;
412}
413
414
415/* Insert a message in a PO file in memory, in the domain and at the position
416   indicated by the iterator.  The iterator thereby advances past the freshly
417   inserted message.  */
418
419void
420po_message_insert (po_message_iterator_t iterator, po_message_t message)
421{
422  message_ty *mp = (message_ty *) message;
423
424  if (iterator->mlp == NULL)
425    /* Now we need to allocate a sublist corresponding to the iterator.  */
426    iterator->mlp =
427      msgdomain_list_sublist (iterator->file->mdlp, iterator->domain, true);
428  /* Insert the message.  */
429  message_list_insert_at (iterator->mlp, iterator->index, mp);
430  /* Advance the iterator.  */
431  iterator->index++;
432}
433
434
435/* Return a freshly constructed message.
436   To finish initializing the message, you must set the msgid and msgstr.  */
437
438po_message_t
439po_message_create (void)
440{
441  lex_pos_ty pos = { NULL, 0 };
442
443  return (po_message_t) message_alloc (NULL, NULL, NULL, 0, &pos);
444}
445
446
447/* Return the msgid (untranslated English string) of a message.  */
448
449const char *
450po_message_msgid (po_message_t message)
451{
452  message_ty *mp = (message_ty *) message;
453
454  return mp->msgid;
455}
456
457
458/* Change the msgid (untranslated English string) of a message.  */
459
460void
461po_message_set_msgid (po_message_t message, const char *msgid)
462{
463  message_ty *mp = (message_ty *) message;
464
465  if (msgid != mp->msgid)
466    {
467      char *old_msgid = (char *) mp->msgid;
468
469      mp->msgid = xstrdup (msgid);
470      if (old_msgid != NULL)
471	free (old_msgid);
472    }
473}
474
475
476/* Return the msgid_plural (untranslated English plural string) of a message,
477   or NULL for a message without plural.  */
478
479const char *
480po_message_msgid_plural (po_message_t message)
481{
482  message_ty *mp = (message_ty *) message;
483
484  return mp->msgid_plural;
485}
486
487
488/* Change the msgid_plural (untranslated English plural string) of a message.
489   NULL means a message without plural.  */
490
491void
492po_message_set_msgid_plural (po_message_t message, const char *msgid_plural)
493{
494  message_ty *mp = (message_ty *) message;
495
496  if (msgid_plural != mp->msgid_plural)
497    {
498      char *old_msgid_plural = (char *) mp->msgid_plural;
499
500      mp->msgid_plural = (msgid_plural != NULL ? xstrdup (msgid_plural) : NULL);
501      if (old_msgid_plural != NULL)
502	free (old_msgid_plural);
503    }
504}
505
506
507/* Return the msgstr (translation) of a message.
508   Return the empty string for an untranslated message.  */
509
510const char *
511po_message_msgstr (po_message_t message)
512{
513  message_ty *mp = (message_ty *) message;
514
515  return mp->msgstr;
516}
517
518
519/* Change the msgstr (translation) of a message.
520   Use an empty string to denote an untranslated message.  */
521
522void
523po_message_set_msgstr (po_message_t message, const char *msgstr)
524{
525  message_ty *mp = (message_ty *) message;
526
527  if (msgstr != mp->msgstr)
528    {
529      char *old_msgstr = (char *) mp->msgstr;
530
531      mp->msgstr = xstrdup (msgstr);
532      mp->msgstr_len = strlen (mp->msgstr) + 1;
533      if (old_msgstr != NULL)
534	free (old_msgstr);
535    }
536}
537
538
539/* Return the msgstr[index] for a message with plural handling, or
540   NULL when the index is out of range or for a message without plural.  */
541
542const char *
543po_message_msgstr_plural (po_message_t message, int index)
544{
545  message_ty *mp = (message_ty *) message;
546
547  if (mp->msgid_plural != NULL && index >= 0)
548    {
549      const char *p;
550      const char *p_end = mp->msgstr + mp->msgstr_len;
551
552      for (p = mp->msgstr; ; p += strlen (p) + 1, index--)
553	{
554	  if (p >= p_end)
555	    return NULL;
556	  if (index == 0)
557	    break;
558	}
559      return p;
560    }
561  else
562    return NULL;
563}
564
565
566/* Change the msgstr[index] for a message with plural handling.
567   Use a NULL value at the end to reduce the number of plural forms.  */
568
569void
570po_message_set_msgstr_plural (po_message_t message, int index, const char *msgstr)
571{
572  message_ty *mp = (message_ty *) message;
573
574  if (mp->msgid_plural != NULL && index >= 0)
575    {
576      char *p = (char *) mp->msgstr;
577      char *p_end = (char *) mp->msgstr + mp->msgstr_len;
578      char *copied_msgstr;
579
580      /* Special care must be taken of the case that msgstr points into the
581	 mp->msgstr string list, because mp->msgstr may be relocated before we
582	 are done with msgstr.  */
583      if (msgstr >= p && msgstr < p_end)
584	msgstr = copied_msgstr = xstrdup (msgstr);
585      else
586	copied_msgstr = NULL;
587
588      for (; ; p += strlen (p) + 1, index--)
589	{
590	  if (p >= p_end)
591	    {
592	      /* Append at the end.  */
593	      if (msgstr != NULL)
594		{
595		  size_t new_msgstr_len = mp->msgstr_len + index + strlen (msgstr) + 1;
596
597		  mp->msgstr =
598		    (char *) xrealloc ((char *) mp->msgstr, new_msgstr_len);
599		  p = (char *) mp->msgstr + mp->msgstr_len;
600		  for (; index > 0; index--)
601		    *p++ = '\0';
602		  memcpy (p, msgstr, strlen (msgstr) + 1);
603		  mp->msgstr_len = new_msgstr_len;
604		}
605	      if (copied_msgstr != NULL)
606		free (copied_msgstr);
607	      return;
608	    }
609	  if (index == 0)
610	    break;
611	}
612      if (msgstr == NULL)
613	{
614	  if (p + strlen (p) + 1 >= p_end)
615	    {
616	      /* Remove the string that starts at p.  */
617	      mp->msgstr_len = p - mp->msgstr;
618	      return;
619	    }
620	  /* It is not possible to remove an element of the string list
621	     except the last one.  So just replace it with the empty string.
622	     That's the best we can do here.  */
623	  msgstr = "";
624	}
625      {
626	/* Replace the string that starts at p.  */
627	size_t i1 = p - mp->msgstr;
628	size_t i2before = i1 + strlen (p);
629	size_t i2after = i1 + strlen (msgstr);
630	size_t new_msgstr_len = mp->msgstr_len - i2before + i2after;
631
632	if (i2after > i2before)
633	  mp->msgstr = (char *) xrealloc ((char *) mp->msgstr, new_msgstr_len);
634	memmove ((char *) mp->msgstr + i2after, mp->msgstr + i2before,
635		 mp->msgstr_len - i2before);
636	memcpy ((char *) mp->msgstr + i1, msgstr, i2after - i1);
637	mp->msgstr_len = new_msgstr_len;
638      }
639      if (copied_msgstr != NULL)
640	free (copied_msgstr);
641    }
642}
643
644
645/* Return the comments for a message.  */
646
647const char *
648po_message_comments (po_message_t message)
649{
650  /* FIXME: memory leak.  */
651  message_ty *mp = (message_ty *) message;
652
653  if (mp->comment == NULL || mp->comment->nitems == 0)
654    return "";
655  else
656    return string_list_join (mp->comment, '\n', '\n', true);
657}
658
659
660/* Change the comments for a message.
661   comments should be a multiline string, ending in a newline, or empty.  */
662
663void
664po_message_set_comments (po_message_t message, const char *comments)
665{
666  message_ty *mp = (message_ty *) message;
667  string_list_ty *slp = string_list_alloc ();
668
669  {
670    char *copy = xstrdup (comments);
671    char *rest;
672
673    rest = copy;
674    while (*rest != '\0')
675      {
676	char *newline = strchr (rest, '\n');
677
678	if (newline != NULL)
679	  {
680	    *newline = '\0';
681	    string_list_append (slp, rest);
682	    rest = newline + 1;
683	  }
684	else
685	  {
686	    string_list_append (slp, rest);
687	    break;
688	  }
689      }
690    free (copy);
691  }
692
693  if (mp->comment != NULL)
694    string_list_free (mp->comment);
695
696  mp->comment = slp;
697}
698
699
700/* Return the extracted comments for a message.  */
701
702const char *
703po_message_extracted_comments (po_message_t message)
704{
705  /* FIXME: memory leak.  */
706  message_ty *mp = (message_ty *) message;
707
708  if (mp->comment_dot == NULL || mp->comment_dot->nitems == 0)
709    return "";
710  else
711    return string_list_join (mp->comment_dot, '\n', '\n', true);
712}
713
714
715/* Change the extracted comments for a message.
716   comments should be a multiline string, ending in a newline, or empty.  */
717
718void
719po_message_set_extracted_comments (po_message_t message, const char *comments)
720{
721  message_ty *mp = (message_ty *) message;
722  string_list_ty *slp = string_list_alloc ();
723
724  {
725    char *copy = xstrdup (comments);
726    char *rest;
727
728    rest = copy;
729    while (*rest != '\0')
730      {
731	char *newline = strchr (rest, '\n');
732
733	if (newline != NULL)
734	  {
735	    *newline = '\0';
736	    string_list_append (slp, rest);
737	    rest = newline + 1;
738	  }
739	else
740	  {
741	    string_list_append (slp, rest);
742	    break;
743	  }
744      }
745    free (copy);
746  }
747
748  if (mp->comment_dot != NULL)
749    string_list_free (mp->comment_dot);
750
751  mp->comment_dot = slp;
752}
753
754
755/* Return the i-th file position for a message, or NULL if i is out of
756   range.  */
757
758po_filepos_t
759po_message_filepos (po_message_t message, int i)
760{
761  message_ty *mp = (message_ty *) message;
762
763  if (i >= 0 && (size_t)i < mp->filepos_count)
764    return (po_filepos_t) &mp->filepos[i];
765  else
766    return NULL;
767}
768
769
770/* Remove the i-th file position from a message.
771   The indices of all following file positions for the message are decremented
772   by one.  */
773
774void
775po_message_remove_filepos (po_message_t message, int i)
776{
777  message_ty *mp = (message_ty *) message;
778
779  if (i >= 0)
780    {
781      size_t j = (size_t)i;
782      size_t n = mp->filepos_count;
783
784      if (j < n)
785	{
786	  mp->filepos_count = n = n - 1;
787	  free ((char *) mp->filepos[j].file_name);
788	  for (; j < n; j++)
789	    mp->filepos[j] = mp->filepos[j + 1];
790	}
791    }
792}
793
794
795/* Add a file position to a message, if it is not already present for the
796   message.
797   file is the file name.
798   start_line is the line number where the string starts, or (size_t)(-1) if no
799   line number is available.  */
800
801void
802po_message_add_filepos (po_message_t message, const char *file, size_t start_line)
803{
804  message_ty *mp = (message_ty *) message;
805
806  message_comment_filepos (mp, file, start_line);
807}
808
809
810/* Return true if the message is marked obsolete.  */
811
812int
813po_message_is_obsolete (po_message_t message)
814{
815  message_ty *mp = (message_ty *) message;
816
817  return (mp->obsolete ? 1 : 0);
818}
819
820
821/* Change the obsolete mark of a message.  */
822
823void
824po_message_set_obsolete (po_message_t message, int obsolete)
825{
826  message_ty *mp = (message_ty *) message;
827
828  mp->obsolete = obsolete;
829}
830
831
832/* Return true if the message is marked fuzzy.  */
833
834int
835po_message_is_fuzzy (po_message_t message)
836{
837  message_ty *mp = (message_ty *) message;
838
839  return (mp->is_fuzzy ? 1 : 0);
840}
841
842
843/* Change the fuzzy mark of a message.  */
844
845void
846po_message_set_fuzzy (po_message_t message, int fuzzy)
847{
848  message_ty *mp = (message_ty *) message;
849
850  mp->is_fuzzy = fuzzy;
851}
852
853
854/* Return true if the message is marked as being a format string of the given
855   type (e.g. "c-format").  */
856
857int
858po_message_is_format (po_message_t message, const char *format_type)
859{
860  message_ty *mp = (message_ty *) message;
861  size_t len = strlen (format_type);
862  size_t i;
863
864  if (len >= 7 && memcmp (format_type + len - 7, "-format", 7) == 0)
865    for (i = 0; i < NFORMATS; i++)
866      if (strlen (format_language[i]) == len - 7
867	  && memcmp (format_language[i], format_type, len - 7) == 0)
868	/* The given format_type corresponds to (enum format_type) i.  */
869	return (possible_format_p (mp->is_format[i]) ? 1 : 0);
870  return 0;
871}
872
873
874/* Change the format string mark for a given type of a message.  */
875
876void
877po_message_set_format (po_message_t message, const char *format_type, /*bool*/int value)
878{
879  message_ty *mp = (message_ty *) message;
880  size_t len = strlen (format_type);
881  size_t i;
882
883  if (len >= 7 && memcmp (format_type + len - 7, "-format", 7) == 0)
884    for (i = 0; i < NFORMATS; i++)
885      if (strlen (format_language[i]) == len - 7
886	  && memcmp (format_language[i], format_type, len - 7) == 0)
887	/* The given format_type corresponds to (enum format_type) i.  */
888	mp->is_format[i] = (value ? yes : no);
889}
890
891
892/* An error logger based on the po_error function pointer.  */
893static void
894po_error_logger (const char *format, ...)
895{
896  va_list args;
897  char *error_message;
898
899  va_start (args, format);
900  if (vasprintf (&error_message, format, args) < 0)
901    error (EXIT_FAILURE, 0, _("memory exhausted"));
902  va_end (args);
903  po_error (0, 0, "%s", error_message);
904  free (error_message);
905}
906
907/* Test whether the message translation is a valid format string if the message
908   is marked as being a format string.  If it is invalid, pass the reasons to
909   the handler.  */
910void
911po_message_check_format (po_message_t message, po_error_handler_t handler)
912{
913  message_ty *mp = (message_ty *) message;
914
915  /* Establish error handler for po_error_logger().  */
916  po_error = handler->error;
917
918  check_msgid_msgstr_format (mp->msgid, mp->msgid_plural,
919			     mp->msgstr, mp->msgstr_len,
920			     mp->is_format, po_error_logger);
921
922  /* Restore error handler.  */
923  po_error = error;
924}
925
926
927/* Return the file name.  */
928
929const char *
930po_filepos_file (po_filepos_t filepos)
931{
932  lex_pos_ty *pp = (lex_pos_ty *) filepos;
933
934  return pp->file_name;
935}
936
937
938/* Return the line number where the string starts, or (size_t)(-1) if no line
939   number is available.  */
940
941size_t
942po_filepos_start_line (po_filepos_t filepos)
943{
944  lex_pos_ty *pp = (lex_pos_ty *) filepos;
945
946  return pp->line_number;
947}
948