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