(root)/
gettext-0.22.4/
gettext-tools/
src/
message.c
       1  /* GNU gettext - internationalization aids
       2     Copyright (C) 1995-1998, 2000-2010, 2012-2013, 2015-2016, 2019-2020, 2023 Free Software Foundation, Inc.
       3  
       4     This file was written by Peter Miller <millerp@canb.auug.org.au>
       5  
       6     This program is free software: you can redistribute it and/or modify
       7     it under the terms of the GNU General Public License as published by
       8     the Free Software Foundation; either version 3 of the License, or
       9     (at your option) any later version.
      10  
      11     This program is distributed in the hope that it will be useful,
      12     but WITHOUT ANY WARRANTY; without even the implied warranty of
      13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      14     GNU General Public License for more details.
      15  
      16     You should have received a copy of the GNU General Public License
      17     along with this program.  If not, see <https://www.gnu.org/licenses/>.  */
      18  
      19  #ifdef HAVE_CONFIG_H
      20  # include <config.h>
      21  #endif
      22  
      23  /* Specification.  */
      24  #include "message.h"
      25  
      26  #include <stdlib.h>
      27  #include <string.h>
      28  
      29  #include "fstrcmp.h"
      30  #include "mem-hash-map.h"
      31  #include "xalloc.h"
      32  #include "xmalloca.h"
      33  
      34  
      35  const char *const format_language[NFORMATS] =
      36  {
      37    /* format_c */                "c",
      38    /* format_objc */             "objc",
      39    /* format_cplusplus_brace */  "c++",
      40    /* format_python */           "python",
      41    /* format_python_brace */     "python-brace",
      42    /* format_java */             "java",
      43    /* format_java_printf */      "java-printf",
      44    /* format_csharp */           "csharp",
      45    /* format_javascript */       "javascript",
      46    /* format_scheme */           "scheme",
      47    /* format_lisp */             "lisp",
      48    /* format_elisp */            "elisp",
      49    /* format_librep */           "librep",
      50    /* format_ruby */             "ruby",
      51    /* format_sh */               "sh",
      52    /* format_awk */              "awk",
      53    /* format_lua */              "lua",
      54    /* format_pascal */           "object-pascal",
      55    /* format_smalltalk */        "smalltalk",
      56    /* format_qt */               "qt",
      57    /* format_qt_plursl */        "qt-plural",
      58    /* format_kde */              "kde",
      59    /* format_kde_kuit */         "kde-kuit",
      60    /* format_boost */            "boost",
      61    /* format_tcl */              "tcl",
      62    /* format_perl */             "perl",
      63    /* format_perl_brace */       "perl-brace",
      64    /* format_php */              "php",
      65    /* format_gcc_internal */     "gcc-internal",
      66    /* format_gfc_internal */     "gfc-internal",
      67    /* format_ycp */              "ycp"
      68  };
      69  
      70  const char *const format_language_pretty[NFORMATS] =
      71  {
      72    /* format_c */                "C",
      73    /* format_objc */             "Objective C",
      74    /* format_cplusplus_brace */  "C++",
      75    /* format_python */           "Python",
      76    /* format_python_brace */     "Python brace",
      77    /* format_java */             "Java MessageFormat",
      78    /* format_java_printf */      "Java printf",
      79    /* format_csharp */           "C#",
      80    /* format_javascript */       "JavaScript",
      81    /* format_scheme */           "Scheme",
      82    /* format_lisp */             "Lisp",
      83    /* format_elisp */            "Emacs Lisp",
      84    /* format_librep */           "librep",
      85    /* format_ruby */             "Ruby",
      86    /* format_sh */               "Shell",
      87    /* format_awk */              "awk",
      88    /* format_lua */              "Lua",
      89    /* format_pascal */           "Object Pascal",
      90    /* format_smalltalk */        "Smalltalk",
      91    /* format_qt */               "Qt",
      92    /* format_qt_plural */        "Qt plural",
      93    /* format_kde */              "KDE",
      94    /* format_kde_kuit */         "KDE KUIT",
      95    /* format_boost */            "Boost",
      96    /* format_tcl */              "Tcl",
      97    /* format_perl */             "Perl",
      98    /* format_perl_brace */       "Perl brace",
      99    /* format_php */              "PHP",
     100    /* format_gcc_internal */     "GCC internal",
     101    /* format_gfc_internal */     "GFC internal",
     102    /* format_ycp */              "YCP"
     103  };
     104  
     105  
     106  bool
     107  possible_format_p (enum is_format is_format)
     108  {
     109    return is_format == possible
     110           || is_format == yes_according_to_context
     111           || is_format == yes;
     112  }
     113  
     114  
     115  const char *const syntax_check_name[NSYNTAXCHECKS] =
     116  {
     117    /* sc_ellipsis_unicode */     "ellipsis-unicode",
     118    /* sc_space_ellipsis */       "space-ellipsis",
     119    /* sc_quote_unicode */        "quote-unicode",
     120    /* sc_bullet_unicode */       "bullet-unicode"
     121  };
     122  
     123  
     124  message_ty *
     125  message_alloc (const char *msgctxt,
     126                 const char *msgid, const char *msgid_plural,
     127                 const char *msgstr, size_t msgstr_len,
     128                 const lex_pos_ty *pp)
     129  {
     130    message_ty *mp;
     131    size_t i;
     132  
     133    mp = XMALLOC (message_ty);
     134    mp->msgctxt = msgctxt;
     135    mp->msgid = msgid;
     136    mp->msgid_plural = (msgid_plural != NULL ? xstrdup (msgid_plural) : NULL);
     137    mp->msgstr = msgstr;
     138    mp->msgstr_len = msgstr_len;
     139    mp->pos = *pp;
     140    mp->comment = NULL;
     141    mp->comment_dot = NULL;
     142    mp->filepos_count = 0;
     143    mp->filepos = NULL;
     144    mp->is_fuzzy = false;
     145    for (i = 0; i < NFORMATS; i++)
     146      mp->is_format[i] = undecided;
     147    mp->range.min = -1;
     148    mp->range.max = -1;
     149    mp->do_wrap = undecided;
     150    for (i = 0; i < NSYNTAXCHECKS; i++)
     151      mp->do_syntax_check[i] = undecided;
     152    mp->prev_msgctxt = NULL;
     153    mp->prev_msgid = NULL;
     154    mp->prev_msgid_plural = NULL;
     155    mp->used = 0;
     156    mp->obsolete = false;
     157    return mp;
     158  }
     159  
     160  
     161  void
     162  message_free (message_ty *mp)
     163  {
     164    size_t j;
     165  
     166    free ((char *) mp->msgid);
     167    if (mp->msgid_plural != NULL)
     168      free ((char *) mp->msgid_plural);
     169    free ((char *) mp->msgstr);
     170    if (mp->comment != NULL)
     171      string_list_free (mp->comment);
     172    if (mp->comment_dot != NULL)
     173      string_list_free (mp->comment_dot);
     174    for (j = 0; j < mp->filepos_count; ++j)
     175      free ((char *) mp->filepos[j].file_name);
     176    if (mp->filepos != NULL)
     177      free (mp->filepos);
     178    if (mp->prev_msgctxt != NULL)
     179      free ((char *) mp->prev_msgctxt);
     180    if (mp->prev_msgid != NULL)
     181      free ((char *) mp->prev_msgid);
     182    if (mp->prev_msgid_plural != NULL)
     183      free ((char *) mp->prev_msgid_plural);
     184    free (mp);
     185  }
     186  
     187  
     188  void
     189  message_comment_append (message_ty *mp, const char *s)
     190  {
     191    if (mp->comment == NULL)
     192      mp->comment = string_list_alloc ();
     193    string_list_append (mp->comment, s);
     194  }
     195  
     196  
     197  void
     198  message_comment_dot_append (message_ty *mp, const char *s)
     199  {
     200    if (mp->comment_dot == NULL)
     201      mp->comment_dot = string_list_alloc ();
     202    string_list_append (mp->comment_dot, s);
     203  }
     204  
     205  
     206  void
     207  message_comment_filepos (message_ty *mp, const char *name, size_t line)
     208  {
     209    size_t j;
     210    size_t nbytes;
     211    lex_pos_ty *pp;
     212  
     213    /* See if we have this position already.  */
     214    for (j = 0; j < mp->filepos_count; j++)
     215      {
     216        pp = &mp->filepos[j];
     217        if (strcmp (pp->file_name, name) == 0 && pp->line_number == line)
     218          return;
     219      }
     220  
     221    /* Extend the list so that we can add a position to it.  */
     222    nbytes = (mp->filepos_count + 1) * sizeof (mp->filepos[0]);
     223    mp->filepos = xrealloc (mp->filepos, nbytes);
     224  
     225    /* Insert the position at the end.  Don't sort the file positions here.  */
     226    pp = &mp->filepos[mp->filepos_count++];
     227    pp->file_name = xstrdup (name);
     228    pp->line_number = line;
     229  }
     230  
     231  
     232  message_ty *
     233  message_copy (message_ty *mp)
     234  {
     235    message_ty *result;
     236    size_t j, i;
     237  
     238    result = message_alloc (mp->msgctxt != NULL ? xstrdup (mp->msgctxt) : NULL,
     239                            xstrdup (mp->msgid), mp->msgid_plural,
     240                            mp->msgstr, mp->msgstr_len, &mp->pos);
     241  
     242    if (mp->comment)
     243      {
     244        for (j = 0; j < mp->comment->nitems; ++j)
     245          message_comment_append (result, mp->comment->item[j]);
     246      }
     247    if (mp->comment_dot)
     248      {
     249        for (j = 0; j < mp->comment_dot->nitems; ++j)
     250          message_comment_dot_append (result, mp->comment_dot->item[j]);
     251      }
     252    result->is_fuzzy = mp->is_fuzzy;
     253    for (i = 0; i < NFORMATS; i++)
     254      result->is_format[i] = mp->is_format[i];
     255    result->range = mp->range;
     256    result->do_wrap = mp->do_wrap;
     257    for (i = 0; i < NSYNTAXCHECKS; i++)
     258      result->do_syntax_check[i] = mp->do_syntax_check[i];
     259    for (j = 0; j < mp->filepos_count; ++j)
     260      {
     261        lex_pos_ty *pp = &mp->filepos[j];
     262        message_comment_filepos (result, pp->file_name, pp->line_number);
     263      }
     264    result->prev_msgctxt =
     265      (mp->prev_msgctxt != NULL ? xstrdup (mp->prev_msgctxt) : NULL);
     266    result->prev_msgid =
     267      (mp->prev_msgid != NULL ? xstrdup (mp->prev_msgid) : NULL);
     268    result->prev_msgid_plural =
     269      (mp->prev_msgid_plural != NULL ? xstrdup (mp->prev_msgid_plural) : NULL);
     270    return result;
     271  }
     272  
     273  
     274  message_list_ty *
     275  message_list_alloc (bool use_hashtable)
     276  {
     277    message_list_ty *mlp;
     278  
     279    mlp = XMALLOC (message_list_ty);
     280    mlp->nitems = 0;
     281    mlp->nitems_max = 0;
     282    mlp->item = NULL;
     283    if ((mlp->use_hashtable = use_hashtable))
     284      hash_init (&mlp->htable, 10);
     285    return mlp;
     286  }
     287  
     288  
     289  void
     290  message_list_free (message_list_ty *mlp, int keep_messages)
     291  {
     292    size_t j;
     293  
     294    if (keep_messages == 0)
     295      for (j = 0; j < mlp->nitems; ++j)
     296        message_free (mlp->item[j]);
     297    if (mlp->item)
     298      free (mlp->item);
     299    if (mlp->use_hashtable)
     300      hash_destroy (&mlp->htable);
     301    free (mlp);
     302  }
     303  
     304  
     305  static int
     306  message_list_hash_insert_entry (hash_table *htable, message_ty *mp)
     307  {
     308    char *alloced_key;
     309    const char *key;
     310    size_t keylen;
     311    int found;
     312  
     313    if (mp->msgctxt != NULL)
     314      {
     315        /* Concatenate mp->msgctxt and mp->msgid, to form the hash table key.  */
     316        size_t msgctxt_len = strlen (mp->msgctxt);
     317        size_t msgid_len = strlen (mp->msgid);
     318        keylen = msgctxt_len + 1 + msgid_len + 1;
     319        alloced_key = (char *) xmalloca (keylen);
     320        memcpy (alloced_key, mp->msgctxt, msgctxt_len);
     321        alloced_key[msgctxt_len] = MSGCTXT_SEPARATOR;
     322        memcpy (alloced_key + msgctxt_len + 1, mp->msgid, msgid_len + 1);
     323        key = alloced_key;
     324      }
     325    else
     326      {
     327        alloced_key = NULL;
     328        key = mp->msgid;
     329        keylen = strlen (mp->msgid) + 1;
     330      }
     331  
     332    found = (hash_insert_entry (htable, key, keylen, mp) == NULL);
     333  
     334    if (mp->msgctxt != NULL)
     335      freea (alloced_key);
     336  
     337    return found;
     338  }
     339  
     340  
     341  void
     342  message_list_append (message_list_ty *mlp, message_ty *mp)
     343  {
     344    if (mlp->nitems >= mlp->nitems_max)
     345      {
     346        size_t nbytes;
     347  
     348        mlp->nitems_max = mlp->nitems_max * 2 + 4;
     349        nbytes = mlp->nitems_max * sizeof (message_ty *);
     350        mlp->item = xrealloc (mlp->item, nbytes);
     351      }
     352    mlp->item[mlp->nitems++] = mp;
     353  
     354    if (mlp->use_hashtable)
     355      if (message_list_hash_insert_entry (&mlp->htable, mp))
     356        /* A message list has duplicates, although it was allocated with the
     357           assertion that it wouldn't have duplicates.  It is a bug.  */
     358        abort ();
     359  }
     360  
     361  
     362  void
     363  message_list_prepend (message_list_ty *mlp, message_ty *mp)
     364  {
     365    size_t j;
     366  
     367    if (mlp->nitems >= mlp->nitems_max)
     368      {
     369        size_t nbytes;
     370  
     371        mlp->nitems_max = mlp->nitems_max * 2 + 4;
     372        nbytes = mlp->nitems_max * sizeof (message_ty *);
     373        mlp->item = xrealloc (mlp->item, nbytes);
     374      }
     375    for (j = mlp->nitems; j > 0; j--)
     376      mlp->item[j] = mlp->item[j - 1];
     377    mlp->item[0] = mp;
     378    mlp->nitems++;
     379  
     380    if (mlp->use_hashtable)
     381      if (message_list_hash_insert_entry (&mlp->htable, mp))
     382        /* A message list has duplicates, although it was allocated with the
     383           assertion that it wouldn't have duplicates.  It is a bug.  */
     384        abort ();
     385  }
     386  
     387  
     388  void
     389  message_list_insert_at (message_list_ty *mlp, size_t n, message_ty *mp)
     390  {
     391    size_t j;
     392  
     393    if (mlp->nitems >= mlp->nitems_max)
     394      {
     395        size_t nbytes;
     396  
     397        mlp->nitems_max = mlp->nitems_max * 2 + 4;
     398        nbytes = mlp->nitems_max * sizeof (message_ty *);
     399        mlp->item = xrealloc (mlp->item, nbytes);
     400      }
     401    for (j = mlp->nitems; j > n; j--)
     402      mlp->item[j] = mlp->item[j - 1];
     403    mlp->item[j] = mp;
     404    mlp->nitems++;
     405  
     406    if (mlp->use_hashtable)
     407      if (message_list_hash_insert_entry (&mlp->htable, mp))
     408        /* A message list has duplicates, although it was allocated with the
     409           assertion that it wouldn't have duplicates.  It is a bug.  */
     410        abort ();
     411  }
     412  
     413  
     414  #if 0 /* unused */
     415  void
     416  message_list_delete_nth (message_list_ty *mlp, size_t n)
     417  {
     418    size_t j;
     419  
     420    if (n >= mlp->nitems)
     421      return;
     422    message_free (mlp->item[n]);
     423    for (j = n + 1; j < mlp->nitems; ++j)
     424      mlp->item[j - 1] = mlp->item[j];
     425    mlp->nitems--;
     426  
     427    if (mlp->use_hashtable)
     428      {
     429        /* Our simple-minded hash tables don't support removal.  */
     430        hash_destroy (&mlp->htable);
     431        mlp->use_hashtable = false;
     432      }
     433  }
     434  #endif
     435  
     436  
     437  void
     438  message_list_remove_if_not (message_list_ty *mlp,
     439                              message_predicate_ty *predicate)
     440  {
     441    size_t i, j;
     442  
     443    for (j = 0, i = 0; j < mlp->nitems; j++)
     444      if (predicate (mlp->item[j]))
     445        mlp->item[i++] = mlp->item[j];
     446    if (mlp->use_hashtable && i < mlp->nitems)
     447      {
     448        /* Our simple-minded hash tables don't support removal.  */
     449        hash_destroy (&mlp->htable);
     450        mlp->use_hashtable = false;
     451      }
     452    mlp->nitems = i;
     453  }
     454  
     455  
     456  bool
     457  message_list_msgids_changed (message_list_ty *mlp)
     458  {
     459    if (mlp->use_hashtable)
     460      {
     461        unsigned long int size = mlp->htable.size;
     462        size_t j;
     463  
     464        hash_destroy (&mlp->htable);
     465        hash_init (&mlp->htable, size);
     466  
     467        for (j = 0; j < mlp->nitems; j++)
     468          {
     469            message_ty *mp = mlp->item[j];
     470  
     471            if (message_list_hash_insert_entry (&mlp->htable, mp))
     472              /* A message list has duplicates, although it was allocated with
     473                 the assertion that it wouldn't have duplicates, and before the
     474                 msgids changed it indeed didn't have duplicates.  */
     475              {
     476                hash_destroy (&mlp->htable);
     477                mlp->use_hashtable = false;
     478                return true;
     479              }
     480          }
     481      }
     482    return false;
     483  }
     484  
     485  
     486  message_list_ty *
     487  message_list_copy (message_list_ty *mlp, int copy_level)
     488  {
     489    message_list_ty *result;
     490    size_t j;
     491  
     492    result = message_list_alloc (mlp->use_hashtable);
     493    for (j = 0; j < mlp->nitems; j++)
     494      {
     495        message_ty *mp = mlp->item[j];
     496  
     497        message_list_append (result, copy_level ? mp : message_copy (mp));
     498      }
     499  
     500    return result;
     501  }
     502  
     503  
     504  message_ty *
     505  message_list_search (message_list_ty *mlp,
     506                       const char *msgctxt, const char *msgid)
     507  {
     508    if (mlp->use_hashtable)
     509      {
     510        char *alloced_key;
     511        const char *key;
     512        size_t keylen;
     513  
     514        if (msgctxt != NULL)
     515          {
     516            /* Concatenate the msgctxt and msgid, to form the hash table key.  */
     517            size_t msgctxt_len = strlen (msgctxt);
     518            size_t msgid_len = strlen (msgid);
     519            keylen = msgctxt_len + 1 + msgid_len + 1;
     520            alloced_key = (char *) xmalloca (keylen);
     521            memcpy (alloced_key, msgctxt, msgctxt_len);
     522            alloced_key[msgctxt_len] = MSGCTXT_SEPARATOR;
     523            memcpy (alloced_key + msgctxt_len + 1, msgid, msgid_len + 1);
     524            key = alloced_key;
     525          }
     526        else
     527          {
     528            alloced_key = NULL;
     529            key = msgid;
     530            keylen = strlen (msgid) + 1;
     531          }
     532  
     533        {
     534          void *htable_value;
     535          int found = !hash_find_entry (&mlp->htable, key, keylen, &htable_value);
     536  
     537          if (msgctxt != NULL)
     538            freea (alloced_key);
     539  
     540          if (found)
     541            return (message_ty *) htable_value;
     542          else
     543            return NULL;
     544        }
     545      }
     546    else
     547      {
     548        size_t j;
     549  
     550        for (j = 0; j < mlp->nitems; ++j)
     551          {
     552            message_ty *mp;
     553  
     554            mp = mlp->item[j];
     555            if ((msgctxt != NULL
     556                 ? mp->msgctxt != NULL && strcmp (msgctxt, mp->msgctxt) == 0
     557                 : mp->msgctxt == NULL)
     558                && strcmp (msgid, mp->msgid) == 0)
     559              return mp;
     560          }
     561        return NULL;
     562      }
     563  }
     564  
     565  
     566  double
     567  fuzzy_search_goal_function (const message_ty *mp,
     568                              const char *msgctxt, const char *msgid,
     569                              double lower_bound)
     570  {
     571    double bonus = 0.0;
     572    /* A translation for a context is a good proposal also for another.  But
     573       give mp a small advantage if mp is valid regardless of any context or
     574       has the same context as the one being looked up.  */
     575    if (mp->msgctxt == NULL
     576        || (msgctxt != NULL && strcmp (msgctxt, mp->msgctxt) == 0))
     577      {
     578        bonus = 0.00001;
     579        /* Since we will consider (weight + bonus) at the end, we are only
     580           interested in weights that are >= lower_bound - bonus.  Subtract
     581           a little more than the bonus, in order to avoid trouble due to
     582           rounding errors.  */
     583        lower_bound -= bonus * 1.01;
     584      }
     585  
     586    {
     587      /* The use of 'volatile' guarantees that excess precision bits are dropped
     588         before the addition and before the following comparison at the caller's
     589         site.  It is necessary on x86 systems where double-floats are not IEEE
     590         compliant by default, to avoid that msgmerge results become platform and
     591         compiler option dependent.  'volatile' is a portable alternative to
     592         gcc's -ffloat-store option.  */
     593      volatile double weight = fstrcmp_bounded (msgid, mp->msgid, lower_bound);
     594  
     595      weight += bonus;
     596  
     597      return weight;
     598    }
     599  }
     600  
     601  
     602  static message_ty *
     603  message_list_search_fuzzy_inner (message_list_ty *mlp,
     604                                   const char *msgctxt, const char *msgid,
     605                                   double *best_weight_p)
     606  {
     607    size_t j;
     608    message_ty *best_mp;
     609  
     610    best_mp = NULL;
     611    for (j = 0; j < mlp->nitems; ++j)
     612      {
     613        message_ty *mp;
     614  
     615        mp = mlp->item[j];
     616  
     617        if (mp->msgstr != NULL && mp->msgstr[0] != '\0')
     618          {
     619            double weight =
     620              fuzzy_search_goal_function (mp, msgctxt, msgid, *best_weight_p);
     621            if (weight > *best_weight_p)
     622              {
     623                *best_weight_p = weight;
     624                best_mp = mp;
     625              }
     626          }
     627      }
     628    return best_mp;
     629  }
     630  
     631  
     632  message_ty *
     633  message_list_search_fuzzy (message_list_ty *mlp,
     634                             const char *msgctxt, const char *msgid)
     635  {
     636    double best_weight;
     637  
     638    best_weight = FUZZY_THRESHOLD;
     639    return message_list_search_fuzzy_inner (mlp, msgctxt, msgid, &best_weight);
     640  }
     641  
     642  
     643  message_list_list_ty *
     644  message_list_list_alloc ()
     645  {
     646    message_list_list_ty *mllp;
     647  
     648    mllp = XMALLOC (message_list_list_ty);
     649    mllp->nitems = 0;
     650    mllp->nitems_max = 0;
     651    mllp->item = NULL;
     652    return mllp;
     653  }
     654  
     655  
     656  void
     657  message_list_list_free (message_list_list_ty *mllp, int keep_level)
     658  {
     659    size_t j;
     660  
     661    if (keep_level < 2)
     662      for (j = 0; j < mllp->nitems; ++j)
     663        message_list_free (mllp->item[j], keep_level);
     664    if (mllp->item)
     665      free (mllp->item);
     666    free (mllp);
     667  }
     668  
     669  
     670  void
     671  message_list_list_append (message_list_list_ty *mllp, message_list_ty *mlp)
     672  {
     673    if (mllp->nitems >= mllp->nitems_max)
     674      {
     675        size_t nbytes;
     676  
     677        mllp->nitems_max = mllp->nitems_max * 2 + 4;
     678        nbytes = mllp->nitems_max * sizeof (message_list_ty *);
     679        mllp->item = xrealloc (mllp->item, nbytes);
     680      }
     681    mllp->item[mllp->nitems++] = mlp;
     682  }
     683  
     684  
     685  void
     686  message_list_list_append_list (message_list_list_ty *mllp,
     687                                 message_list_list_ty *mllp2)
     688  {
     689    size_t j;
     690  
     691    for (j = 0; j < mllp2->nitems; ++j)
     692      message_list_list_append (mllp, mllp2->item[j]);
     693  }
     694  
     695  
     696  message_ty *
     697  message_list_list_search (message_list_list_ty *mllp,
     698                            const char *msgctxt, const char *msgid)
     699  {
     700    message_ty *best_mp;
     701    int best_weight; /* 0: not found, 1: found without msgstr, 2: translated */
     702    size_t j;
     703  
     704    best_mp = NULL;
     705    best_weight = 0;
     706    for (j = 0; j < mllp->nitems; ++j)
     707      {
     708        message_list_ty *mlp;
     709        message_ty *mp;
     710  
     711        mlp = mllp->item[j];
     712        mp = message_list_search (mlp, msgctxt, msgid);
     713        if (mp)
     714          {
     715            int weight = (mp->msgstr_len == 1 && mp->msgstr[0] == '\0' ? 1 : 2);
     716            if (weight > best_weight)
     717              {
     718                best_mp = mp;
     719                best_weight = weight;
     720              }
     721          }
     722      }
     723    return best_mp;
     724  }
     725  
     726  
     727  #if 0 /* unused */
     728  message_ty *
     729  message_list_list_search_fuzzy (message_list_list_ty *mllp,
     730                                  const char *msgctxt, const char *msgid)
     731  {
     732    size_t j;
     733    double best_weight;
     734    message_ty *best_mp;
     735  
     736    best_weight = FUZZY_THRESHOLD;
     737    best_mp = NULL;
     738    for (j = 0; j < mllp->nitems; ++j)
     739      {
     740        message_list_ty *mlp;
     741        message_ty *mp;
     742  
     743        mlp = mllp->item[j];
     744        mp = message_list_search_fuzzy_inner (mlp, msgctxt, msgid, &best_weight);
     745        if (mp)
     746          best_mp = mp;
     747      }
     748    return best_mp;
     749  }
     750  #endif
     751  
     752  
     753  msgdomain_ty*
     754  msgdomain_alloc (const char *domain, bool use_hashtable)
     755  {
     756    msgdomain_ty *mdp;
     757  
     758    mdp = XMALLOC (msgdomain_ty);
     759    mdp->domain = domain;
     760    mdp->messages = message_list_alloc (use_hashtable);
     761    return mdp;
     762  }
     763  
     764  
     765  void
     766  msgdomain_free (msgdomain_ty *mdp)
     767  {
     768    message_list_free (mdp->messages, 0);
     769    free (mdp);
     770  }
     771  
     772  
     773  msgdomain_list_ty *
     774  msgdomain_list_alloc (bool use_hashtable)
     775  {
     776    msgdomain_list_ty *mdlp;
     777  
     778    mdlp = XMALLOC (msgdomain_list_ty);
     779    /* Put the default domain first, so that when we output it,
     780       we can omit the 'domain' directive.  */
     781    mdlp->nitems = 1;
     782    mdlp->nitems_max = 1;
     783    mdlp->item = XNMALLOC (mdlp->nitems_max, msgdomain_ty *);
     784    mdlp->item[0] = msgdomain_alloc (MESSAGE_DOMAIN_DEFAULT, use_hashtable);
     785    mdlp->use_hashtable = use_hashtable;
     786    mdlp->encoding = NULL;
     787    return mdlp;
     788  }
     789  
     790  
     791  void
     792  msgdomain_list_free (msgdomain_list_ty *mdlp)
     793  {
     794    size_t j;
     795  
     796    for (j = 0; j < mdlp->nitems; ++j)
     797      msgdomain_free (mdlp->item[j]);
     798    if (mdlp->item)
     799      free (mdlp->item);
     800    free (mdlp);
     801  }
     802  
     803  
     804  void
     805  msgdomain_list_append (msgdomain_list_ty *mdlp, msgdomain_ty *mdp)
     806  {
     807    if (mdlp->nitems >= mdlp->nitems_max)
     808      {
     809        size_t nbytes;
     810  
     811        mdlp->nitems_max = mdlp->nitems_max * 2 + 4;
     812        nbytes = mdlp->nitems_max * sizeof (msgdomain_ty *);
     813        mdlp->item = xrealloc (mdlp->item, nbytes);
     814      }
     815    mdlp->item[mdlp->nitems++] = mdp;
     816  }
     817  
     818  
     819  #if 0 /* unused */
     820  void
     821  msgdomain_list_append_list (msgdomain_list_ty *mdlp, msgdomain_list_ty *mdlp2)
     822  {
     823    size_t j;
     824  
     825    for (j = 0; j < mdlp2->nitems; ++j)
     826      msgdomain_list_append (mdlp, mdlp2->item[j]);
     827  }
     828  #endif
     829  
     830  
     831  message_list_ty *
     832  msgdomain_list_sublist (msgdomain_list_ty *mdlp, const char *domain,
     833                          bool create)
     834  {
     835    size_t j;
     836  
     837    for (j = 0; j < mdlp->nitems; j++)
     838      if (strcmp (mdlp->item[j]->domain, domain) == 0)
     839        return mdlp->item[j]->messages;
     840  
     841    if (create)
     842      {
     843        msgdomain_ty *mdp = msgdomain_alloc (domain, mdlp->use_hashtable);
     844        msgdomain_list_append (mdlp, mdp);
     845        return mdp->messages;
     846      }
     847    else
     848      return NULL;
     849  }
     850  
     851  
     852  /* Copy a message domain list.
     853     If copy_level = 0, also copy the messages.  If copy_level = 1, share the
     854     messages but copy the domains.  If copy_level = 2, share the domains.  */
     855  msgdomain_list_ty *
     856  msgdomain_list_copy (msgdomain_list_ty *mdlp, int copy_level)
     857  {
     858    msgdomain_list_ty *result;
     859    size_t j;
     860  
     861    result = XMALLOC (msgdomain_list_ty);
     862    result->nitems = 0;
     863    result->nitems_max = 0;
     864    result->item = NULL;
     865    result->use_hashtable = mdlp->use_hashtable;
     866    result->encoding = mdlp->encoding;
     867  
     868    for (j = 0; j < mdlp->nitems; j++)
     869      {
     870        msgdomain_ty *mdp = mdlp->item[j];
     871  
     872        if (copy_level < 2)
     873          {
     874            msgdomain_ty *result_mdp = XMALLOC (msgdomain_ty);
     875  
     876            result_mdp->domain = mdp->domain;
     877            result_mdp->messages = message_list_copy (mdp->messages, copy_level);
     878  
     879            msgdomain_list_append (result, result_mdp);
     880          }
     881        else
     882          msgdomain_list_append (result, mdp);
     883      }
     884  
     885    return result;
     886  }
     887  
     888  
     889  #if 0 /* unused */
     890  message_ty *
     891  msgdomain_list_search (msgdomain_list_ty *mdlp,
     892                         const char *msgctxt, const char *msgid)
     893  {
     894    size_t j;
     895  
     896    for (j = 0; j < mdlp->nitems; ++j)
     897      {
     898        msgdomain_ty *mdp;
     899        message_ty *mp;
     900  
     901        mdp = mdlp->item[j];
     902        mp = message_list_search (mdp->messages, msgctxt, msgid);
     903        if (mp)
     904          return mp;
     905      }
     906    return NULL;
     907  }
     908  #endif
     909  
     910  
     911  #if 0 /* unused */
     912  message_ty *
     913  msgdomain_list_search_fuzzy (msgdomain_list_ty *mdlp,
     914                               const char *msgctxt, const char *msgid)
     915  {
     916    size_t j;
     917    double best_weight;
     918    message_ty *best_mp;
     919  
     920    best_weight = FUZZY_THRESHOLD;
     921    best_mp = NULL;
     922    for (j = 0; j < mdlp->nitems; ++j)
     923      {
     924        msgdomain_ty *mdp;
     925        message_ty *mp;
     926  
     927        mdp = mdlp->item[j];
     928        mp = message_list_search_fuzzy_inner (mdp->messages, msgctxt, msgid,
     929                                              &best_weight);
     930        if (mp)
     931          best_mp = mp;
     932      }
     933    return best_mp;
     934  }
     935  #endif