Home | History | Annotate | Line # | Download | only in libgettextpo
      1 /* Public API for GNU gettext PO files.
      2    Copyright (C) 2003-2006 Free Software Foundation, Inc.
      3    Written by Bruno Haible <bruno (at) 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
     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
     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
    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
    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
    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
    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
    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 *
    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 *
    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 *
    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 *
    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
    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
    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
    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
    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
    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 *
    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
    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 *
    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
    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 *
    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
    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 *
    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
    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 *
    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
    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 *
    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
    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 *
    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
    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
    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
    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
    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 *
    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
    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 *
    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
    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 *
    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
    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
   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
   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
   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
   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
   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
   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 *
   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
   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
   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
   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
   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
   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
   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