(root)/
gettext-0.22.4/
gettext-tools/
libgettextpo/
gettext-po.c
       1  /* Public API for GNU gettext PO files.
       2     Copyright (C) 2003-2010, 2014, 2023 Free Software Foundation, Inc.
       3     Written by Bruno Haible <bruno@clisp.org>, 2003.
       4  
       5     This program is free software: you can redistribute it and/or modify
       6     it under the terms of the GNU General Public License as published by
       7     the Free Software Foundation; either version 3 of the License, or
       8     (at your option) any later version.
       9  
      10     This program is distributed in the hope that it will be useful,
      11     but WITHOUT ANY WARRANTY; without even the implied warranty of
      12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      13     GNU General Public License for more details.
      14  
      15     You should have received a copy of the GNU General Public License
      16     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      17  
      18  #ifdef HAVE_CONFIG_H
      19  # include <config.h>
      20  #endif
      21  /* Avoid side effect of gnulib's error.h on 'struct po_error_handler'.  */
      22  #define _GL_NO_INLINE_ERROR
      23  
      24  /* Avoid side effect of config.h on 'struct po_error_handler'.  */
      25  #include "error.h"
      26  static void (*orig_error) (int status, int errnum,
      27                             const char *format, ...)
      28    = error;
      29  
      30  static void (*orig_error_at_line) (int status, int errnum,
      31                                     const char *filename, unsigned int lineno,
      32                                     const char *format, ...)
      33    = error_at_line;
      34  #undef error
      35  #undef error_at_line
      36  
      37  /* Specification.  */
      38  #include "gettext-po.h"
      39  
      40  #include <limits.h>
      41  #include <stdbool.h>
      42  #include <stdio.h>
      43  #include <stdlib.h>
      44  #include <stdarg.h>
      45  #include <string.h>
      46  
      47  #include "message.h"
      48  #include "xalloc.h"
      49  #include "read-catalog.h"
      50  #include "read-po.h"
      51  #include "write-catalog.h"
      52  #include "write-po.h"
      53  #include "xerror.h"
      54  #include "po-error.h"
      55  #include "po-xerror.h"
      56  #include "format.h"
      57  #include "xvasprintf.h"
      58  #include "msgl-check.h"
      59  #include "gettext.h"
      60  
      61  #define _(str) gettext(str)
      62  
      63  
      64  struct po_file
      65  {
      66    msgdomain_list_ty *mdlp;
      67    const char *real_filename;
      68    const char *logical_filename;
      69    const char **domains;
      70  };
      71  
      72  struct po_message_iterator
      73  {
      74    po_file_t file;
      75    char *domain;
      76    message_list_ty *mlp;
      77    size_t index;
      78  };
      79  
      80  /* A po_message_t is actually a 'struct message_ty *'.  */
      81  
      82  /* A po_filepos_t is actually a 'lex_pos_ty *'.  */
      83  
      84  
      85  /* Version number: (major<<16) + (minor<<8) + subminor */
      86  int libgettextpo_version = LIBGETTEXTPO_VERSION;
      87  
      88  
      89  /* Create an empty PO file representation in memory.  */
      90  
      91  po_file_t
      92  po_file_create (void)
      93  {
      94    po_file_t file;
      95  
      96    file = XMALLOC (struct po_file);
      97    file->mdlp = msgdomain_list_alloc (false);
      98    file->real_filename = _("<unnamed>");
      99    file->logical_filename = file->real_filename;
     100    file->domains = NULL;
     101    return file;
     102  }
     103  
     104  
     105  /* Read a PO file into memory.
     106     Return its contents.  Upon failure, return NULL and set errno.  */
     107  
     108  po_file_t
     109  po_file_read (const char *filename, po_xerror_handler_t handler)
     110  {
     111    FILE *fp;
     112    po_file_t file;
     113  
     114    if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0)
     115      {
     116        filename = _("<stdin>");
     117        fp = stdin;
     118      }
     119    else
     120      {
     121        fp = fopen (filename, "r");
     122        if (fp == NULL)
     123          return NULL;
     124      }
     125  
     126    /* Establish error handler around read_catalog_stream().  */
     127    po_xerror =
     128      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
     129      handler->xerror;
     130    po_xerror2 =
     131      (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 *))
     132      handler->xerror2;
     133    gram_max_allowed_errors = UINT_MAX;
     134  
     135    file = XMALLOC (struct po_file);
     136    file->real_filename = filename;
     137    file->logical_filename = filename;
     138    file->mdlp = read_catalog_stream (fp, file->real_filename,
     139                                      file->logical_filename, &input_format_po);
     140    file->domains = NULL;
     141  
     142    /* Restore error handler.  */
     143    po_xerror  = textmode_xerror;
     144    po_xerror2 = textmode_xerror2;
     145    gram_max_allowed_errors = 20;
     146  
     147    if (fp != stdin)
     148      fclose (fp);
     149    return file;
     150  }
     151  #undef po_file_read
     152  
     153  #ifdef __cplusplus
     154  extern "C" po_file_t po_file_read_v2 (const char *filename, po_error_handler_t handler);
     155  #endif
     156  po_file_t
     157  po_file_read_v2 (const char *filename, po_error_handler_t handler)
     158  {
     159    FILE *fp;
     160    po_file_t file;
     161  
     162    if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0)
     163      {
     164        filename = _("<stdin>");
     165        fp = stdin;
     166      }
     167    else
     168      {
     169        fp = fopen (filename, "r");
     170        if (fp == NULL)
     171          return NULL;
     172      }
     173  
     174    /* Establish error handler around read_catalog_stream().  */
     175    po_error             = handler->error;
     176    po_error_at_line     = handler->error_at_line;
     177    po_multiline_warning = handler->multiline_warning;
     178    po_multiline_error   = handler->multiline_error;
     179    gram_max_allowed_errors = UINT_MAX;
     180  
     181    file = XMALLOC (struct po_file);
     182    file->real_filename = filename;
     183    file->logical_filename = filename;
     184    file->mdlp = read_catalog_stream (fp, file->real_filename,
     185                                      file->logical_filename, &input_format_po);
     186    file->domains = NULL;
     187  
     188    /* Restore error handler.  */
     189    po_error             = orig_error;
     190    po_error_at_line     = orig_error_at_line;
     191    po_multiline_warning = multiline_warning;
     192    po_multiline_error   = multiline_error;
     193    gram_max_allowed_errors = 20;
     194  
     195    if (fp != stdin)
     196      fclose (fp);
     197    return file;
     198  }
     199  
     200  /* Older version for binary backward compatibility.  */
     201  #ifdef __cplusplus
     202  extern "C" po_file_t po_file_read (const char *filename);
     203  #endif
     204  po_file_t
     205  po_file_read (const char *filename)
     206  {
     207    FILE *fp;
     208    po_file_t file;
     209  
     210    if (strcmp (filename, "-") == 0 || strcmp (filename, "/dev/stdin") == 0)
     211      {
     212        filename = _("<stdin>");
     213        fp = stdin;
     214      }
     215    else
     216      {
     217        fp = fopen (filename, "r");
     218        if (fp == NULL)
     219          return NULL;
     220      }
     221  
     222    file = XMALLOC (struct po_file);
     223    file->real_filename = filename;
     224    file->logical_filename = filename;
     225    file->mdlp = read_catalog_stream (fp, file->real_filename,
     226                                      file->logical_filename, &input_format_po);
     227    file->domains = NULL;
     228  
     229    if (fp != stdin)
     230      fclose (fp);
     231    return file;
     232  }
     233  
     234  
     235  /* Write an in-memory PO file to a file.
     236     Upon failure, return NULL and set errno.  */
     237  
     238  po_file_t
     239  po_file_write (po_file_t file, const char *filename, po_xerror_handler_t handler)
     240  {
     241    /* Establish error handler around msgdomain_list_print().  */
     242    po_xerror =
     243      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
     244      handler->xerror;
     245    po_xerror2 =
     246      (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 *))
     247      handler->xerror2;
     248  
     249    msgdomain_list_print (file->mdlp, filename, &output_format_po, true, false);
     250  
     251    /* Restore error handler.  */
     252    po_xerror  = textmode_xerror;
     253    po_xerror2 = textmode_xerror2;
     254  
     255    return file;
     256  }
     257  #undef po_file_write
     258  
     259  /* Older version for binary backward compatibility.  */
     260  #ifdef __cplusplus
     261  extern "C" po_file_t po_file_write (po_file_t file, const char *filename, po_error_handler_t handler);
     262  #endif
     263  po_file_t
     264  po_file_write (po_file_t file, const char *filename, po_error_handler_t handler)
     265  {
     266    /* Establish error handler around msgdomain_list_print().  */
     267    po_error             = handler->error;
     268    po_error_at_line     = handler->error_at_line;
     269    po_multiline_warning = handler->multiline_warning;
     270    po_multiline_error   = handler->multiline_error;
     271  
     272    msgdomain_list_print (file->mdlp, filename, &output_format_po, true, false);
     273  
     274    /* Restore error handler.  */
     275    po_error             = orig_error;
     276    po_error_at_line     = orig_error_at_line;
     277    po_multiline_warning = multiline_warning;
     278    po_multiline_error   = multiline_error;
     279  
     280    return file;
     281  }
     282  
     283  
     284  /* Free a PO file from memory.  */
     285  
     286  void
     287  po_file_free (po_file_t file)
     288  {
     289    msgdomain_list_free (file->mdlp);
     290    if (file->domains != NULL)
     291      free (file->domains);
     292    free (file);
     293  }
     294  
     295  
     296  /* Return the names of the domains covered by a PO file in memory.  */
     297  
     298  const char * const *
     299  po_file_domains (po_file_t file)
     300  {
     301    if (file->domains == NULL)
     302      {
     303        size_t n = file->mdlp->nitems;
     304        const char **domains = XNMALLOC (n + 1, const char *);
     305        size_t j;
     306  
     307        for (j = 0; j < n; j++)
     308          domains[j] = file->mdlp->item[j]->domain;
     309        domains[n] = NULL;
     310  
     311        file->domains = domains;
     312      }
     313  
     314    return file->domains;
     315  }
     316  
     317  
     318  /* Return the header entry of a domain of a PO file in memory.
     319     The domain NULL denotes the default domain.
     320     Return NULL if there is no header entry.  */
     321  
     322  const char *
     323  po_file_domain_header (po_file_t file, const char *domain)
     324  {
     325    message_list_ty *mlp;
     326    size_t j;
     327  
     328    if (domain == NULL)
     329      domain = MESSAGE_DOMAIN_DEFAULT;
     330    mlp = msgdomain_list_sublist (file->mdlp, domain, false);
     331    if (mlp != NULL)
     332      for (j = 0; j < mlp->nitems; j++)
     333        if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
     334          {
     335            const char *header = mlp->item[j]->msgstr;
     336  
     337            if (header != NULL)
     338              return xstrdup (header);
     339            else
     340              return NULL;
     341          }
     342    return NULL;
     343  }
     344  
     345  
     346  /* Return the value of a field in a header entry.
     347     The return value is either a freshly allocated string, to be freed by the
     348     caller, or NULL.  */
     349  
     350  char *
     351  po_header_field (const char *header, const char *field)
     352  {
     353    size_t field_len = strlen (field);
     354    const char *line;
     355  
     356    for (line = header;;)
     357      {
     358        if (strncmp (line, field, field_len) == 0 && line[field_len] == ':')
     359          {
     360            const char *value_start;
     361            const char *value_end;
     362            char *value;
     363  
     364            value_start = line + field_len + 1;
     365            if (*value_start == ' ')
     366              value_start++;
     367            value_end = strchr (value_start, '\n');
     368            if (value_end == NULL)
     369              value_end = value_start + strlen (value_start);
     370  
     371            value = XNMALLOC (value_end - value_start + 1, char);
     372            memcpy (value, value_start, value_end - value_start);
     373            value[value_end - value_start] = '\0';
     374  
     375            return value;
     376          }
     377  
     378        line = strchr (line, '\n');
     379        if (line != NULL)
     380          line++;
     381        else
     382          break;
     383      }
     384  
     385    return NULL;
     386  }
     387  
     388  
     389  /* Return the header entry with a given field set to a given value.  The field
     390     is added if necessary.
     391     The return value is a freshly allocated string.  */
     392  
     393  char *
     394  po_header_set_field (const char *header, const char *field, const char *value)
     395  {
     396    size_t header_len = strlen (header);
     397    size_t field_len = strlen (field);
     398    size_t value_len = strlen (value);
     399  
     400    {
     401      const char *line;
     402  
     403      for (line = header;;)
     404        {
     405          if (strncmp (line, field, field_len) == 0 && line[field_len] == ':')
     406            {
     407              const char *oldvalue_start;
     408              const char *oldvalue_end;
     409              size_t header_part1_len;
     410              size_t header_part3_len;
     411              size_t result_len;
     412              char *result;
     413  
     414              oldvalue_start = line + field_len + 1;
     415              if (*oldvalue_start == ' ')
     416                oldvalue_start++;
     417              oldvalue_end = strchr (oldvalue_start, '\n');
     418              if (oldvalue_end == NULL)
     419                oldvalue_end = oldvalue_start + strlen (oldvalue_start);
     420  
     421              header_part1_len = oldvalue_start - header;
     422              header_part3_len = header + header_len - oldvalue_end;
     423              result_len = header_part1_len + value_len + header_part3_len;
     424                      /* = header_len - oldvalue_len + value_len */
     425              result = XNMALLOC (result_len + 1, char);
     426              memcpy (result, header, header_part1_len);
     427              memcpy (result + header_part1_len, value, value_len);
     428              memcpy (result + header_part1_len + value_len, oldvalue_end,
     429                      header_part3_len);
     430              *(result + result_len) = '\0';
     431  
     432              return result;
     433            }
     434  
     435          line = strchr (line, '\n');
     436          if (line != NULL)
     437            line++;
     438          else
     439            break;
     440        }
     441    }
     442    {
     443      size_t newline;
     444      size_t result_len;
     445      char *result;
     446  
     447      newline = (header_len > 0 && header[header_len - 1] != '\n' ? 1 : 0);
     448      result_len = header_len + newline + field_len + 2 + value_len + 1;
     449      result = XNMALLOC (result_len + 1, char);
     450      memcpy (result, header, header_len);
     451      if (newline)
     452        *(result + header_len) = '\n';
     453      memcpy (result + header_len + newline, field, field_len);
     454      *(result + header_len + newline + field_len) = ':';
     455      *(result + header_len + newline + field_len + 1) = ' ';
     456      memcpy (result + header_len + newline + field_len + 2, value, value_len);
     457      *(result + header_len + newline + field_len + 2 + value_len) = '\n';
     458      *(result + result_len) = '\0';
     459  
     460      return result;
     461    }
     462  }
     463  
     464  
     465  /* Create an iterator for traversing a domain of a PO file in memory.
     466     The domain NULL denotes the default domain.  */
     467  
     468  po_message_iterator_t
     469  po_message_iterator (po_file_t file, const char *domain)
     470  {
     471    po_message_iterator_t iterator;
     472  
     473    if (domain == NULL)
     474      domain = MESSAGE_DOMAIN_DEFAULT;
     475  
     476    iterator = XMALLOC (struct po_message_iterator);
     477    iterator->file = file;
     478    iterator->domain = xstrdup (domain);
     479    iterator->mlp = msgdomain_list_sublist (file->mdlp, domain, false);
     480    iterator->index = 0;
     481  
     482    return iterator;
     483  }
     484  
     485  
     486  /* Free an iterator.  */
     487  
     488  void
     489  po_message_iterator_free (po_message_iterator_t iterator)
     490  {
     491    free (iterator->domain);
     492    free (iterator);
     493  }
     494  
     495  
     496  /* Return the next message, and advance the iterator.
     497     Return NULL at the end of the message list.  */
     498  
     499  po_message_t
     500  po_next_message (po_message_iterator_t iterator)
     501  {
     502    if (iterator->mlp != NULL && iterator->index < iterator->mlp->nitems)
     503      return (po_message_t) iterator->mlp->item[iterator->index++];
     504    else
     505      return NULL;
     506  }
     507  
     508  
     509  /* Insert a message in a PO file in memory, in the domain and at the position
     510     indicated by the iterator.  The iterator thereby advances past the freshly
     511     inserted message.  */
     512  
     513  void
     514  po_message_insert (po_message_iterator_t iterator, po_message_t message)
     515  {
     516    message_ty *mp = (message_ty *) message;
     517  
     518    if (iterator->mlp == NULL)
     519      /* Now we need to allocate a sublist corresponding to the iterator.  */
     520      iterator->mlp =
     521        msgdomain_list_sublist (iterator->file->mdlp, iterator->domain, true);
     522    /* Insert the message.  */
     523    message_list_insert_at (iterator->mlp, iterator->index, mp);
     524    /* Advance the iterator.  */
     525    iterator->index++;
     526  }
     527  
     528  
     529  /* Return a freshly constructed message.
     530     To finish initializing the message, you must set the msgid and msgstr.  */
     531  
     532  po_message_t
     533  po_message_create (void)
     534  {
     535    lex_pos_ty pos = { NULL, 0 };
     536  
     537    return (po_message_t) message_alloc (NULL, NULL, NULL, xstrdup (""), 1, &pos);
     538  }
     539  
     540  
     541  /* Return the context of a message, or NULL for a message not restricted to a
     542     context.  */
     543  const char *
     544  po_message_msgctxt (po_message_t message)
     545  {
     546    message_ty *mp = (message_ty *) message;
     547  
     548    return mp->msgctxt;
     549  }
     550  
     551  
     552  /* Change the context of a message. NULL means a message not restricted to a
     553     context.  */
     554  void
     555  po_message_set_msgctxt (po_message_t message, const char *msgctxt)
     556  {
     557    message_ty *mp = (message_ty *) message;
     558  
     559    if (msgctxt != mp->msgctxt)
     560      {
     561        char *old_msgctxt = (char *) mp->msgctxt;
     562  
     563        mp->msgctxt = (msgctxt != NULL ? xstrdup (msgctxt) : NULL);
     564        if (old_msgctxt != NULL)
     565          free (old_msgctxt);
     566      }
     567  }
     568  
     569  
     570  /* Return the msgid (untranslated English string) of a message.  */
     571  
     572  const char *
     573  po_message_msgid (po_message_t message)
     574  {
     575    message_ty *mp = (message_ty *) message;
     576  
     577    return mp->msgid;
     578  }
     579  
     580  
     581  /* Change the msgid (untranslated English string) of a message.  */
     582  
     583  void
     584  po_message_set_msgid (po_message_t message, const char *msgid)
     585  {
     586    message_ty *mp = (message_ty *) message;
     587  
     588    if (msgid != mp->msgid)
     589      {
     590        char *old_msgid = (char *) mp->msgid;
     591  
     592        mp->msgid = xstrdup (msgid);
     593        if (old_msgid != NULL)
     594          free (old_msgid);
     595      }
     596  }
     597  
     598  
     599  /* Return the msgid_plural (untranslated English plural string) of a message,
     600     or NULL for a message without plural.  */
     601  
     602  const char *
     603  po_message_msgid_plural (po_message_t message)
     604  {
     605    message_ty *mp = (message_ty *) message;
     606  
     607    return mp->msgid_plural;
     608  }
     609  
     610  
     611  /* Change the msgid_plural (untranslated English plural string) of a message.
     612     NULL means a message without plural.  */
     613  
     614  void
     615  po_message_set_msgid_plural (po_message_t message, const char *msgid_plural)
     616  {
     617    message_ty *mp = (message_ty *) message;
     618  
     619    if (msgid_plural != mp->msgid_plural)
     620      {
     621        char *old_msgid_plural = (char *) mp->msgid_plural;
     622  
     623        mp->msgid_plural = (msgid_plural != NULL ? xstrdup (msgid_plural) : NULL);
     624        if (old_msgid_plural != NULL)
     625          free (old_msgid_plural);
     626      }
     627  }
     628  
     629  
     630  /* Return the msgstr (translation) of a message.
     631     Return the empty string for an untranslated message.  */
     632  
     633  const char *
     634  po_message_msgstr (po_message_t message)
     635  {
     636    message_ty *mp = (message_ty *) message;
     637  
     638    return mp->msgstr;
     639  }
     640  
     641  
     642  /* Change the msgstr (translation) of a message.
     643     Use an empty string to denote an untranslated message.  */
     644  
     645  void
     646  po_message_set_msgstr (po_message_t message, const char *msgstr)
     647  {
     648    message_ty *mp = (message_ty *) message;
     649  
     650    if (msgstr != mp->msgstr)
     651      {
     652        char *old_msgstr = (char *) mp->msgstr;
     653  
     654        mp->msgstr = xstrdup (msgstr);
     655        mp->msgstr_len = strlen (mp->msgstr) + 1;
     656        if (old_msgstr != NULL)
     657          free (old_msgstr);
     658      }
     659  }
     660  
     661  
     662  /* Return the msgstr[index] for a message with plural handling, or
     663     NULL when the index is out of range or for a message without plural.  */
     664  
     665  const char *
     666  po_message_msgstr_plural (po_message_t message, int index)
     667  {
     668    message_ty *mp = (message_ty *) message;
     669  
     670    if (mp->msgid_plural != NULL && index >= 0)
     671      {
     672        const char *p;
     673        const char *p_end = mp->msgstr + mp->msgstr_len;
     674  
     675        for (p = mp->msgstr; ; p += strlen (p) + 1, index--)
     676          {
     677            if (p >= p_end)
     678              return NULL;
     679            if (index == 0)
     680              break;
     681          }
     682        return p;
     683      }
     684    else
     685      return NULL;
     686  }
     687  
     688  
     689  /* Change the msgstr[index] for a message with plural handling.
     690     Use a NULL value at the end to reduce the number of plural forms.  */
     691  
     692  void
     693  po_message_set_msgstr_plural (po_message_t message, int index, const char *msgstr)
     694  {
     695    message_ty *mp = (message_ty *) message;
     696  
     697    if (mp->msgid_plural != NULL && index >= 0)
     698      {
     699        char *p = (char *) mp->msgstr;
     700        char *p_end = (char *) mp->msgstr + mp->msgstr_len;
     701        char *copied_msgstr;
     702  
     703        /* Special care must be taken of the case that msgstr points into the
     704           mp->msgstr string list, because mp->msgstr may be relocated before we
     705           are done with msgstr.  */
     706        if (msgstr >= p && msgstr < p_end)
     707          msgstr = copied_msgstr = xstrdup (msgstr);
     708        else
     709          copied_msgstr = NULL;
     710  
     711        for (; ; p += strlen (p) + 1, index--)
     712          {
     713            if (p >= p_end)
     714              {
     715                /* Append at the end.  */
     716                if (msgstr != NULL)
     717                  {
     718                    size_t new_msgstr_len = mp->msgstr_len + index + strlen (msgstr) + 1;
     719  
     720                    mp->msgstr =
     721                      (char *) xrealloc ((char *) mp->msgstr, new_msgstr_len);
     722                    p = (char *) mp->msgstr + mp->msgstr_len;
     723                    for (; index > 0; index--)
     724                      *p++ = '\0';
     725                    memcpy (p, msgstr, strlen (msgstr) + 1);
     726                    mp->msgstr_len = new_msgstr_len;
     727                  }
     728                if (copied_msgstr != NULL)
     729                  free (copied_msgstr);
     730                return;
     731              }
     732            if (index == 0)
     733              break;
     734          }
     735        if (msgstr == NULL)
     736          {
     737            if (p + strlen (p) + 1 >= p_end)
     738              {
     739                /* Remove the string that starts at p.  */
     740                mp->msgstr_len = p - mp->msgstr;
     741                return;
     742              }
     743            /* It is not possible to remove an element of the string list
     744               except the last one.  So just replace it with the empty string.
     745               That's the best we can do here.  */
     746            msgstr = "";
     747          }
     748        {
     749          /* Replace the string that starts at p.  */
     750          size_t i1 = p - mp->msgstr;
     751          size_t i2before = i1 + strlen (p);
     752          size_t i2after = i1 + strlen (msgstr);
     753          size_t new_msgstr_len = mp->msgstr_len - i2before + i2after;
     754  
     755          if (i2after > i2before)
     756            mp->msgstr = (char *) xrealloc ((char *) mp->msgstr, new_msgstr_len);
     757          memmove ((char *) mp->msgstr + i2after, mp->msgstr + i2before,
     758                   mp->msgstr_len - i2before);
     759          memcpy ((char *) mp->msgstr + i1, msgstr, i2after - i1);
     760          mp->msgstr_len = new_msgstr_len;
     761        }
     762        if (copied_msgstr != NULL)
     763          free (copied_msgstr);
     764      }
     765  }
     766  
     767  
     768  /* Return the comments for a message.  */
     769  
     770  const char *
     771  po_message_comments (po_message_t message)
     772  {
     773    /* FIXME: memory leak.  */
     774    message_ty *mp = (message_ty *) message;
     775  
     776    if (mp->comment == NULL || mp->comment->nitems == 0)
     777      return "";
     778    else
     779      return string_list_join (mp->comment, "\n", '\n', true);
     780  }
     781  
     782  
     783  /* Change the comments for a message.
     784     comments should be a multiline string, ending in a newline, or empty.  */
     785  
     786  void
     787  po_message_set_comments (po_message_t message, const char *comments)
     788  {
     789    message_ty *mp = (message_ty *) message;
     790    string_list_ty *slp = string_list_alloc ();
     791  
     792    {
     793      char *copy = xstrdup (comments);
     794      char *rest;
     795  
     796      rest = copy;
     797      while (*rest != '\0')
     798        {
     799          char *newline = strchr (rest, '\n');
     800  
     801          if (newline != NULL)
     802            {
     803              *newline = '\0';
     804              string_list_append (slp, rest);
     805              rest = newline + 1;
     806            }
     807          else
     808            {
     809              string_list_append (slp, rest);
     810              break;
     811            }
     812        }
     813      free (copy);
     814    }
     815  
     816    if (mp->comment != NULL)
     817      string_list_free (mp->comment);
     818  
     819    mp->comment = slp;
     820  }
     821  
     822  
     823  /* Return the extracted comments for a message.  */
     824  
     825  const char *
     826  po_message_extracted_comments (po_message_t message)
     827  {
     828    /* FIXME: memory leak.  */
     829    message_ty *mp = (message_ty *) message;
     830  
     831    if (mp->comment_dot == NULL || mp->comment_dot->nitems == 0)
     832      return "";
     833    else
     834      return string_list_join (mp->comment_dot, "\n", '\n', true);
     835  }
     836  
     837  
     838  /* Change the extracted comments for a message.
     839     comments should be a multiline string, ending in a newline, or empty.  */
     840  
     841  void
     842  po_message_set_extracted_comments (po_message_t message, const char *comments)
     843  {
     844    message_ty *mp = (message_ty *) message;
     845    string_list_ty *slp = string_list_alloc ();
     846  
     847    {
     848      char *copy = xstrdup (comments);
     849      char *rest;
     850  
     851      rest = copy;
     852      while (*rest != '\0')
     853        {
     854          char *newline = strchr (rest, '\n');
     855  
     856          if (newline != NULL)
     857            {
     858              *newline = '\0';
     859              string_list_append (slp, rest);
     860              rest = newline + 1;
     861            }
     862          else
     863            {
     864              string_list_append (slp, rest);
     865              break;
     866            }
     867        }
     868      free (copy);
     869    }
     870  
     871    if (mp->comment_dot != NULL)
     872      string_list_free (mp->comment_dot);
     873  
     874    mp->comment_dot = slp;
     875  }
     876  
     877  
     878  /* Return the i-th file position for a message, or NULL if i is out of
     879     range.  */
     880  
     881  po_filepos_t
     882  po_message_filepos (po_message_t message, int i)
     883  {
     884    message_ty *mp = (message_ty *) message;
     885  
     886    if (i >= 0 && (size_t)i < mp->filepos_count)
     887      return (po_filepos_t) &mp->filepos[i];
     888    else
     889      return NULL;
     890  }
     891  
     892  
     893  /* Remove the i-th file position from a message.
     894     The indices of all following file positions for the message are decremented
     895     by one.  */
     896  
     897  void
     898  po_message_remove_filepos (po_message_t message, int i)
     899  {
     900    message_ty *mp = (message_ty *) message;
     901  
     902    if (i >= 0)
     903      {
     904        size_t j = (size_t)i;
     905        size_t n = mp->filepos_count;
     906  
     907        if (j < n)
     908          {
     909            mp->filepos_count = n = n - 1;
     910            free ((char *) mp->filepos[j].file_name);
     911            for (; j < n; j++)
     912              mp->filepos[j] = mp->filepos[j + 1];
     913          }
     914      }
     915  }
     916  
     917  
     918  /* Add a file position to a message, if it is not already present for the
     919     message.
     920     file is the file name.
     921     start_line is the line number where the string starts, or (size_t)(-1) if no
     922     line number is available.  */
     923  
     924  void
     925  po_message_add_filepos (po_message_t message, const char *file, size_t start_line)
     926  {
     927    message_ty *mp = (message_ty *) message;
     928  
     929    message_comment_filepos (mp, file, start_line);
     930  }
     931  
     932  
     933  /* Return the previous context of a message, or NULL for none.  */
     934  
     935  const char *
     936  po_message_prev_msgctxt (po_message_t message)
     937  {
     938    message_ty *mp = (message_ty *) message;
     939  
     940    return mp->prev_msgctxt;
     941  }
     942  
     943  
     944  /* Change the previous context of a message.  NULL is allowed.  */
     945  
     946  void
     947  po_message_set_prev_msgctxt (po_message_t message, const char *prev_msgctxt)
     948  {
     949    message_ty *mp = (message_ty *) message;
     950  
     951    if (prev_msgctxt != mp->prev_msgctxt)
     952      {
     953        char *old_prev_msgctxt = (char *) mp->prev_msgctxt;
     954  
     955        mp->prev_msgctxt = (prev_msgctxt != NULL ? xstrdup (prev_msgctxt) : NULL);
     956        if (old_prev_msgctxt != NULL)
     957          free (old_prev_msgctxt);
     958      }
     959  }
     960  
     961  
     962  /* Return the previous msgid (untranslated English string) of a message, or
     963     NULL for none.  */
     964  
     965  const char *
     966  po_message_prev_msgid (po_message_t message)
     967  {
     968    message_ty *mp = (message_ty *) message;
     969  
     970    return mp->prev_msgid;
     971  }
     972  
     973  
     974  /* Change the previous msgid (untranslated English string) of a message.
     975     NULL is allowed.  */
     976  
     977  void
     978  po_message_set_prev_msgid (po_message_t message, const char *prev_msgid)
     979  {
     980    message_ty *mp = (message_ty *) message;
     981  
     982    if (prev_msgid != mp->prev_msgid)
     983      {
     984        char *old_prev_msgid = (char *) mp->prev_msgid;
     985  
     986        mp->prev_msgid = (prev_msgid != NULL ? xstrdup (prev_msgid) : NULL);
     987        if (old_prev_msgid != NULL)
     988          free (old_prev_msgid);
     989      }
     990  }
     991  
     992  
     993  /* Return the previous msgid_plural (untranslated English plural string) of a
     994     message, or NULL for none.  */
     995  
     996  const char *
     997  po_message_prev_msgid_plural (po_message_t message)
     998  {
     999    message_ty *mp = (message_ty *) message;
    1000  
    1001    return mp->prev_msgid_plural;
    1002  }
    1003  
    1004  
    1005  /* Change the previous msgid_plural (untranslated English plural string) of a
    1006     message.  NULL is allowed.  */
    1007  
    1008  void
    1009  po_message_set_prev_msgid_plural (po_message_t message, const char *prev_msgid_plural)
    1010  {
    1011    message_ty *mp = (message_ty *) message;
    1012  
    1013    if (prev_msgid_plural != mp->prev_msgid_plural)
    1014      {
    1015        char *old_prev_msgid_plural = (char *) mp->prev_msgid_plural;
    1016  
    1017        mp->prev_msgid_plural =
    1018          (prev_msgid_plural != NULL ? xstrdup (prev_msgid_plural) : NULL);
    1019        if (old_prev_msgid_plural != NULL)
    1020          free (old_prev_msgid_plural);
    1021      }
    1022  }
    1023  
    1024  
    1025  /* Return true if the message is marked obsolete.  */
    1026  
    1027  int
    1028  po_message_is_obsolete (po_message_t message)
    1029  {
    1030    message_ty *mp = (message_ty *) message;
    1031  
    1032    return (mp->obsolete ? 1 : 0);
    1033  }
    1034  
    1035  
    1036  /* Change the obsolete mark of a message.  */
    1037  
    1038  void
    1039  po_message_set_obsolete (po_message_t message, int obsolete)
    1040  {
    1041    message_ty *mp = (message_ty *) message;
    1042  
    1043    mp->obsolete = obsolete;
    1044  }
    1045  
    1046  
    1047  /* Return true if the message is marked fuzzy.  */
    1048  
    1049  int
    1050  po_message_is_fuzzy (po_message_t message)
    1051  {
    1052    message_ty *mp = (message_ty *) message;
    1053  
    1054    return (mp->is_fuzzy ? 1 : 0);
    1055  }
    1056  
    1057  
    1058  /* Change the fuzzy mark of a message.  */
    1059  
    1060  void
    1061  po_message_set_fuzzy (po_message_t message, int fuzzy)
    1062  {
    1063    message_ty *mp = (message_ty *) message;
    1064  
    1065    mp->is_fuzzy = fuzzy;
    1066  }
    1067  
    1068  
    1069  /* Return true if the message is marked as being a format string of the given
    1070     type (e.g. "c-format").  */
    1071  
    1072  int
    1073  po_message_is_format (po_message_t message, const char *format_type)
    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          return (possible_format_p (mp->is_format[i]) ? 1 : 0);
    1085    return 0;
    1086  }
    1087  
    1088  
    1089  /* Change the format string mark for a given type of a message.  */
    1090  
    1091  void
    1092  po_message_set_format (po_message_t message, const char *format_type, /*bool*/int value)
    1093  {
    1094    message_ty *mp = (message_ty *) message;
    1095    size_t len = strlen (format_type);
    1096    size_t i;
    1097  
    1098    if (len >= 7 && memcmp (format_type + len - 7, "-format", 7) == 0)
    1099      for (i = 0; i < NFORMATS; i++)
    1100        if (strlen (format_language[i]) == len - 7
    1101            && memcmp (format_language[i], format_type, len - 7) == 0)
    1102          /* The given format_type corresponds to (enum format_type) i.  */
    1103          mp->is_format[i] = (value ? yes : no);
    1104  }
    1105  
    1106  
    1107  /* If a numeric range of a message is set, return true and store the minimum
    1108     and maximum value in *MINP and *MAXP.  */
    1109  
    1110  int
    1111  po_message_is_range (po_message_t message, int *minp, int *maxp)
    1112  {
    1113    message_ty *mp = (message_ty *) message;
    1114  
    1115    if (has_range_p (mp->range))
    1116      {
    1117        *minp = mp->range.min;
    1118        *maxp = mp->range.max;
    1119        return 1;
    1120      }
    1121    else
    1122      return 0;
    1123  }
    1124  
    1125  
    1126  /* Change the numeric range of a message.  MIN and MAX must be non-negative,
    1127     with MIN < MAX.  Use MIN = MAX = -1 to remove the numeric range of a
    1128     message.  */
    1129  
    1130  void
    1131  po_message_set_range (po_message_t message, int min, int max)
    1132  {
    1133    message_ty *mp = (message_ty *) message;
    1134  
    1135    if (min >= 0 && max >= min)
    1136      {
    1137        mp->range.min = min;
    1138        mp->range.max = max;
    1139      }
    1140    else if (min < 0 && max < 0)
    1141      {
    1142        mp->range.min = -1;
    1143        mp->range.max = -1;
    1144      }
    1145    /* Other values of min and max are invalid.  */
    1146  }
    1147  
    1148  
    1149  /* Return the file name.  */
    1150  
    1151  const char *
    1152  po_filepos_file (po_filepos_t filepos)
    1153  {
    1154    lex_pos_ty *pp = (lex_pos_ty *) filepos;
    1155  
    1156    return pp->file_name;
    1157  }
    1158  
    1159  
    1160  /* Return the line number where the string starts, or (size_t)(-1) if no line
    1161     number is available.  */
    1162  
    1163  size_t
    1164  po_filepos_start_line (po_filepos_t filepos)
    1165  {
    1166    lex_pos_ty *pp = (lex_pos_ty *) filepos;
    1167  
    1168    return pp->line_number;
    1169  }
    1170  
    1171  
    1172  /* Return a NULL terminated array of the supported format types.  */
    1173  
    1174  const char * const *
    1175  po_format_list (void)
    1176  {
    1177    static const char * const * whole_list /* = NULL */;
    1178    if (whole_list == NULL)
    1179      {
    1180        const char **list = XNMALLOC (NFORMATS + 1, const char *);
    1181        size_t i;
    1182        for (i = 0; i < NFORMATS; i++)
    1183          list[i] = xasprintf ("%s-format", format_language[i]);
    1184        list[i] = NULL;
    1185        whole_list = list;
    1186      }
    1187    return whole_list;
    1188  }
    1189  
    1190  
    1191  /* Return the pretty name associated with a format type.
    1192     For example, for "csharp-format", return "C#".
    1193     Return NULL if the argument is not a supported format type.  */
    1194  
    1195  const char *
    1196  po_format_pretty_name (const char *format_type)
    1197  {
    1198    size_t len = strlen (format_type);
    1199    size_t i;
    1200  
    1201    if (len >= 7 && memcmp (format_type + len - 7, "-format", 7) == 0)
    1202      for (i = 0; i < NFORMATS; i++)
    1203        if (strlen (format_language[i]) == len - 7
    1204            && memcmp (format_language[i], format_type, len - 7) == 0)
    1205          /* The given format_type corresponds to (enum format_type) i.  */
    1206          return format_language_pretty[i];
    1207    return NULL;
    1208  }
    1209  
    1210  
    1211  /* Test whether an entire file PO file is valid, like msgfmt does it.
    1212     If it is invalid, pass the reasons to the handler.  */
    1213  
    1214  void
    1215  po_file_check_all (po_file_t file, po_xerror_handler_t handler)
    1216  {
    1217    msgdomain_list_ty *mdlp;
    1218    size_t k;
    1219  
    1220    /* Establish error handler.  */
    1221    po_xerror =
    1222      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
    1223      handler->xerror;
    1224    po_xerror2 =
    1225      (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 *))
    1226      handler->xerror2;
    1227  
    1228    mdlp = file->mdlp;
    1229    for (k = 0; k < mdlp->nitems; k++)
    1230      check_message_list (mdlp->item[k]->messages, 1, 1, 1, 1, 1, 0, 0, 0);
    1231  
    1232    /* Restore error handler.  */
    1233    po_xerror  = textmode_xerror;
    1234    po_xerror2 = textmode_xerror2;
    1235  }
    1236  
    1237  
    1238  /* Test a single message, to be inserted in a PO file in memory, like msgfmt
    1239     does it.  If it is invalid, pass the reasons to the handler.  The iterator
    1240     is not modified by this call; it only specifies the file and the domain.  */
    1241  
    1242  void
    1243  po_message_check_all (po_message_t message, po_message_iterator_t iterator,
    1244                        po_xerror_handler_t handler)
    1245  {
    1246    message_ty *mp = (message_ty *) message;
    1247  
    1248    /* Establish error handler.  */
    1249    po_xerror =
    1250      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
    1251      handler->xerror;
    1252    po_xerror2 =
    1253      (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 *))
    1254      handler->xerror2;
    1255  
    1256    /* For plural checking, combine the message and its header into a small,
    1257       two-element message list.  */
    1258    {
    1259      message_ty *header;
    1260  
    1261      /* Find the header.  */
    1262      {
    1263        message_list_ty *mlp;
    1264        size_t j;
    1265  
    1266        header = NULL;
    1267        mlp =
    1268          msgdomain_list_sublist (iterator->file->mdlp, iterator->domain, false);
    1269        if (mlp != NULL)
    1270          for (j = 0; j < mlp->nitems; j++)
    1271            if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
    1272              {
    1273                header = mlp->item[j];
    1274                break;
    1275              }
    1276      }
    1277  
    1278      {
    1279        message_ty *items[2];
    1280        struct message_list_ty ml;
    1281        ml.item = items;
    1282        ml.nitems = 0;
    1283        ml.nitems_max = 2;
    1284        ml.use_hashtable = false;
    1285  
    1286        if (header != NULL)
    1287          message_list_append (&ml, header);
    1288        if (mp != header)
    1289          message_list_append (&ml, mp);
    1290  
    1291        check_message_list (&ml, 1, 1, 1, 1, 1, 0, 0, 0);
    1292      }
    1293    }
    1294  
    1295    /* Restore error handler.  */
    1296    po_xerror  = textmode_xerror;
    1297    po_xerror2 = textmode_xerror2;
    1298  }
    1299  
    1300  
    1301  /* Test whether the message translation is a valid format string if the message
    1302     is marked as being a format string.  If it is invalid, pass the reasons to
    1303     the handler.  */
    1304  void
    1305  po_message_check_format (po_message_t message, po_xerror_handler_t handler)
    1306  {
    1307    message_ty *mp = (message_ty *) message;
    1308  
    1309    /* Establish error handler.  */
    1310    po_xerror =
    1311      (void (*) (int, const message_ty *, const char *, size_t, size_t, int, const char *))
    1312      handler->xerror;
    1313    po_xerror2 =
    1314      (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 *))
    1315      handler->xerror2;
    1316  
    1317    if (!mp->obsolete)
    1318      check_message (mp, &mp->pos, 0, 1, NULL, 0, 0, 0, 0);
    1319  
    1320    /* Restore error handler.  */
    1321    po_xerror  = textmode_xerror;
    1322    po_xerror2 = textmode_xerror2;
    1323  }
    1324  #undef po_message_check_format
    1325  
    1326  /* Older version for binary backward compatibility.  */
    1327  
    1328  /* An error logger based on the po_error function pointer.  */
    1329  static void
    1330  po_error_logger (const char *format, ...)
    1331       __attribute__ ((__format__ (__printf__, 1, 2)));
    1332  static void
    1333  po_error_logger (const char *format, ...)
    1334  {
    1335    va_list args;
    1336    char *error_message;
    1337  
    1338    va_start (args, format);
    1339    if (vasprintf (&error_message, format, args) < 0)
    1340      orig_error (EXIT_FAILURE, 0, _("memory exhausted"));
    1341    va_end (args);
    1342    po_error (0, 0, "%s", error_message);
    1343    free (error_message);
    1344  }
    1345  
    1346  /* Test whether the message translation is a valid format string if the message
    1347     is marked as being a format string.  If it is invalid, pass the reasons to
    1348     the handler.  */
    1349  #ifdef __cplusplus
    1350  extern "C" void po_message_check_format (po_message_t message, po_error_handler_t handler);
    1351  #endif
    1352  void
    1353  po_message_check_format (po_message_t message, po_error_handler_t handler)
    1354  {
    1355    message_ty *mp = (message_ty *) message;
    1356  
    1357    /* Establish error handler for po_error_logger().  */
    1358    po_error = handler->error;
    1359  
    1360    check_msgid_msgstr_format (mp->msgid, mp->msgid_plural,
    1361                               mp->msgstr, mp->msgstr_len,
    1362                               mp->is_format, mp->range, NULL, po_error_logger);
    1363  
    1364    /* Restore error handler.  */
    1365    po_error = orig_error;
    1366  }