• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Message list concatenation and duplicate handling.
2    Copyright (C) 2001-2003, 2005-2008, 2012, 2015, 2019-2020 Free Software
3    Foundation, Inc.
4    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
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 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 #include <alloca.h>
24 
25 /* Specification.  */
26 #include "msgl-cat.h"
27 
28 #include <limits.h>
29 #include <stdbool.h>
30 #include <stddef.h>
31 #include <stdlib.h>
32 #include <string.h>
33 
34 #include "error.h"
35 #include "xerror.h"
36 #include "xvasprintf.h"
37 #include "message.h"
38 #include "read-catalog.h"
39 #include "po-charset.h"
40 #include "msgl-ascii.h"
41 #include "msgl-equal.h"
42 #include "msgl-iconv.h"
43 #include "xalloc.h"
44 #include "xmalloca.h"
45 #include "c-strstr.h"
46 #include "basename-lgpl.h"
47 #include "gettext.h"
48 
49 #define _(str) gettext (str)
50 
51 
52 /* These variables control which messages are selected.  */
53 int more_than;
54 int less_than;
55 
56 /* If true, use the first available translation.
57    If false, merge all available translations into one and fuzzy it.  */
58 bool use_first;
59 
60 /* If true, merge like msgcomm.
61    If false, merge like msgcat and msguniq.  */
62 bool msgcomm_mode = false;
63 
64 /* If true, omit the header entry.
65    If false, keep the header entry present in the input.  */
66 bool omit_header = false;
67 
68 
69 static bool
is_message_selected(const message_ty * tmp)70 is_message_selected (const message_ty *tmp)
71 {
72   int used = (tmp->used >= 0 ? tmp->used : - tmp->used);
73 
74   return (is_header (tmp)
75           ? !omit_header        /* keep the header entry */
76           : (used > more_than && used < less_than));
77 }
78 
79 
80 static bool
is_message_needed(const message_ty * mp)81 is_message_needed (const message_ty *mp)
82 {
83   if (!msgcomm_mode
84       && ((!is_header (mp) && mp->is_fuzzy) || mp->msgstr[0] == '\0'))
85     /* Weak translation.  Needed if there are only weak translations.  */
86     return mp->tmp->used < 0 && is_message_selected (mp->tmp);
87   else
88     /* Good translation.  */
89     return is_message_selected (mp->tmp);
90 }
91 
92 
93 /* The use_first logic.  */
94 static bool
is_message_first_needed(const message_ty * mp)95 is_message_first_needed (const message_ty *mp)
96 {
97   if (mp->tmp->obsolete && is_message_needed (mp))
98     {
99       mp->tmp->obsolete = false;
100       return true;
101     }
102   else
103     return false;
104 }
105 
106 
107 msgdomain_list_ty *
catenate_msgdomain_list(string_list_ty * file_list,catalog_input_format_ty input_syntax,const char * to_code)108 catenate_msgdomain_list (string_list_ty *file_list,
109                          catalog_input_format_ty input_syntax,
110                          const char *to_code)
111 {
112   const char * const *files = file_list->item;
113   size_t nfiles = file_list->nitems;
114   msgdomain_list_ty **mdlps;
115   const char ***canon_charsets;
116   const char ***identifications;
117   msgdomain_list_ty *total_mdlp;
118   const char *canon_to_code;
119   size_t n, j;
120 
121   /* Read input files.  */
122   mdlps = XNMALLOC (nfiles, msgdomain_list_ty *);
123   for (n = 0; n < nfiles; n++)
124     mdlps[n] = read_catalog_file (files[n], input_syntax);
125 
126   /* Determine the canonical name of each input file's encoding.  */
127   canon_charsets = XNMALLOC (nfiles, const char **);
128   for (n = 0; n < nfiles; n++)
129     {
130       msgdomain_list_ty *mdlp = mdlps[n];
131       size_t k;
132 
133       canon_charsets[n] = XNMALLOC (mdlp->nitems, const char *);
134       for (k = 0; k < mdlp->nitems; k++)
135         {
136           message_list_ty *mlp = mdlp->item[k]->messages;
137           const char *canon_from_code = NULL;
138 
139           if (mlp->nitems > 0)
140             {
141               for (j = 0; j < mlp->nitems; j++)
142                 if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
143                   {
144                     const char *header = mlp->item[j]->msgstr;
145 
146                     if (header != NULL)
147                       {
148                         const char *charsetstr = c_strstr (header, "charset=");
149 
150                         if (charsetstr != NULL)
151                           {
152                             size_t len;
153                             char *charset;
154                             const char *canon_charset;
155 
156                             charsetstr += strlen ("charset=");
157                             len = strcspn (charsetstr, " \t\n");
158                             charset = (char *) xmalloca (len + 1);
159                             memcpy (charset, charsetstr, len);
160                             charset[len] = '\0';
161 
162                             canon_charset = po_charset_canonicalize (charset);
163                             if (canon_charset == NULL)
164                               {
165                                 /* Don't give an error for POT files, because
166                                    POT files usually contain only ASCII
167                                    msgids.  */
168                                 const char *filename = files[n];
169                                 size_t filenamelen = strlen (filename);
170 
171                                 if (filenamelen >= 4
172                                     && memcmp (filename + filenamelen - 4,
173                                                ".pot", 4) == 0
174                                     && strcmp (charset, "CHARSET") == 0)
175                                   canon_charset = po_charset_ascii;
176                                 else
177                                   error (EXIT_FAILURE, 0,
178                                          _("present charset \"%s\" is not a portable encoding name"),
179                                          charset);
180                               }
181 
182                             freea (charset);
183 
184                             if (canon_from_code == NULL)
185                               canon_from_code = canon_charset;
186                             else if (canon_from_code != canon_charset)
187                               error (EXIT_FAILURE, 0,
188                                      _("two different charsets \"%s\" and \"%s\" in input file"),
189                                      canon_from_code, canon_charset);
190                           }
191                       }
192                   }
193               if (canon_from_code == NULL)
194                 {
195                   if (is_ascii_message_list (mlp))
196                     canon_from_code = po_charset_ascii;
197                   else if (mdlp->encoding != NULL)
198                     canon_from_code = mdlp->encoding;
199                   else
200                     {
201                       if (k == 0)
202                         error (EXIT_FAILURE, 0,
203                                _("input file '%s' doesn't contain a header entry with a charset specification"),
204                                files[n]);
205                       else
206                         error (EXIT_FAILURE, 0,
207                                _("domain \"%s\" in input file '%s' doesn't contain a header entry with a charset specification"),
208                                mdlp->item[k]->domain, files[n]);
209                     }
210                 }
211             }
212           canon_charsets[n][k] = canon_from_code;
213         }
214     }
215 
216   /* Determine textual identifications of each file/domain combination.  */
217   identifications = XNMALLOC (nfiles, const char **);
218   for (n = 0; n < nfiles; n++)
219     {
220       const char *filename = last_component (files[n]);
221       msgdomain_list_ty *mdlp = mdlps[n];
222       size_t k;
223 
224       identifications[n] = XNMALLOC (mdlp->nitems, const char *);
225       for (k = 0; k < mdlp->nitems; k++)
226         {
227           const char *domain = mdlp->item[k]->domain;
228           message_list_ty *mlp = mdlp->item[k]->messages;
229           char *project_id = NULL;
230 
231           for (j = 0; j < mlp->nitems; j++)
232             if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
233               {
234                 const char *header = mlp->item[j]->msgstr;
235 
236                 if (header != NULL)
237                   {
238                     const char *cp = c_strstr (header, "Project-Id-Version:");
239 
240                     if (cp != NULL)
241                       {
242                         const char *endp;
243 
244                         cp += sizeof ("Project-Id-Version:") - 1;
245 
246                         endp = strchr (cp, '\n');
247                         if (endp == NULL)
248                           endp = cp + strlen (cp);
249 
250                         while (cp < endp && *cp == ' ')
251                           cp++;
252 
253                         if (cp < endp)
254                           {
255                             size_t len = endp - cp;
256                             project_id = XNMALLOC (len + 1, char);
257                             memcpy (project_id, cp, len);
258                             project_id[len] = '\0';
259                           }
260                         break;
261                       }
262                   }
263               }
264 
265           identifications[n][k] =
266             (project_id != NULL
267              ? (k > 0 ? xasprintf ("%s:%s (%s)", filename, domain, project_id)
268                       : xasprintf ("%s (%s)", filename, project_id))
269              : (k > 0 ? xasprintf ("%s:%s", filename, domain)
270                       : xasprintf ("%s", filename)));
271         }
272     }
273 
274   /* Create list of resulting messages, but don't fill it.  Only count
275      the number of translations for each message.
276      If for a message, there is at least one non-fuzzy, non-empty translation,
277      use only the non-fuzzy, non-empty translations.  Otherwise use the
278      fuzzy or empty translations as well.  */
279   total_mdlp = msgdomain_list_alloc (true);
280   for (n = 0; n < nfiles; n++)
281     {
282       msgdomain_list_ty *mdlp = mdlps[n];
283       size_t k;
284 
285       for (k = 0; k < mdlp->nitems; k++)
286         {
287           const char *domain = mdlp->item[k]->domain;
288           message_list_ty *mlp = mdlp->item[k]->messages;
289           message_list_ty *total_mlp;
290 
291           total_mlp = msgdomain_list_sublist (total_mdlp, domain, true);
292 
293           for (j = 0; j < mlp->nitems; j++)
294             {
295               message_ty *mp = mlp->item[j];
296               message_ty *tmp;
297               size_t i;
298 
299               tmp = message_list_search (total_mlp, mp->msgctxt, mp->msgid);
300               if (tmp != NULL)
301                 {
302                   if ((tmp->msgid_plural != NULL) != (mp->msgid_plural != NULL))
303                     {
304                       char *errormsg =
305                         xasprintf (_("msgid '%s' is used without plural and with plural."),
306                                    mp->msgid);
307                       multiline_error (xstrdup (""),
308                                        xasprintf ("%s\n", errormsg));
309                     }
310                 }
311               else
312                 {
313                   tmp = message_alloc (mp->msgctxt, mp->msgid, mp->msgid_plural,
314                                        NULL, 0, &mp->pos);
315                   tmp->is_fuzzy = true; /* may be set to false later */
316                   for (i = 0; i < NFORMATS; i++)
317                     tmp->is_format[i] = undecided; /* may be set to yes/no later */
318                   tmp->range.min = - INT_MAX;
319                   tmp->range.max = - INT_MAX;
320                   tmp->do_wrap = yes; /* may be set to no later */
321                   for (i = 0; i < NSYNTAXCHECKS; i++)
322                     tmp->do_syntax_check[i] = undecided; /* may be set to yes/no later */
323                   tmp->obsolete = true; /* may be set to false later */
324                   tmp->alternative_count = 0;
325                   tmp->alternative = NULL;
326                   message_list_append (total_mlp, tmp);
327                 }
328 
329               if (!msgcomm_mode
330                   && ((!is_header (mp) && mp->is_fuzzy)
331                       || mp->msgstr[0] == '\0'))
332                 /* Weak translation.  Counted as negative tmp->used.  */
333                 {
334                   if (tmp->used <= 0)
335                     tmp->used--;
336                 }
337               else
338                 /* Good translation.  Counted as positive tmp->used.  */
339                 {
340                   if (tmp->used < 0)
341                     tmp->used = 0;
342                   tmp->used++;
343                 }
344               mp->tmp = tmp;
345             }
346         }
347     }
348 
349   /* Remove messages that are not used and need not be converted.  */
350   for (n = 0; n < nfiles; n++)
351     {
352       msgdomain_list_ty *mdlp = mdlps[n];
353       size_t k;
354 
355       for (k = 0; k < mdlp->nitems; k++)
356         {
357           message_list_ty *mlp = mdlp->item[k]->messages;
358 
359           message_list_remove_if_not (mlp,
360                                       use_first
361                                       ? is_message_first_needed
362                                       : is_message_needed);
363 
364           /* If no messages are remaining, drop the charset.  */
365           if (mlp->nitems == 0)
366             canon_charsets[n][k] = NULL;
367         }
368     }
369   {
370     size_t k;
371 
372     for (k = 0; k < total_mdlp->nitems; k++)
373       {
374         message_list_ty *mlp = total_mdlp->item[k]->messages;
375 
376         message_list_remove_if_not (mlp, is_message_selected);
377       }
378   }
379 
380   /* Determine the common known a-priori encoding, if any.  */
381   if (nfiles > 0)
382     {
383       bool all_same_encoding = true;
384 
385       for (n = 1; n < nfiles; n++)
386         if (mdlps[n]->encoding != mdlps[0]->encoding)
387           {
388             all_same_encoding = false;
389             break;
390           }
391 
392       if (all_same_encoding)
393         total_mdlp->encoding = mdlps[0]->encoding;
394     }
395 
396   /* Determine the target encoding for the remaining messages.  */
397   if (to_code != NULL)
398     {
399       /* Canonicalize target encoding.  */
400       canon_to_code = po_charset_canonicalize (to_code);
401       if (canon_to_code == NULL)
402         error (EXIT_FAILURE, 0,
403                _("target charset \"%s\" is not a portable encoding name."),
404                to_code);
405     }
406   else
407     {
408       /* No target encoding was specified.  Test whether the messages are
409          all in a single encoding.  If so, conversion is not needed.  */
410       const char *first = NULL;
411       const char *second = NULL;
412       bool with_ASCII = false;
413       bool with_UTF8 = false;
414       bool all_ASCII_compatible = true;
415 
416       for (n = 0; n < nfiles; n++)
417         {
418           msgdomain_list_ty *mdlp = mdlps[n];
419           size_t k;
420 
421           for (k = 0; k < mdlp->nitems; k++)
422             if (canon_charsets[n][k] != NULL)
423               {
424                 if (canon_charsets[n][k] == po_charset_ascii)
425                   with_ASCII = true;
426                 else
427                   {
428                     if (first == NULL)
429                       first = canon_charsets[n][k];
430                     else if (canon_charsets[n][k] != first && second == NULL)
431                       second = canon_charsets[n][k];
432 
433                     if (strcmp (canon_charsets[n][k], "UTF-8") == 0)
434                       with_UTF8 = true;
435 
436                     if (!po_charset_ascii_compatible (canon_charsets[n][k]))
437                       all_ASCII_compatible = false;
438                   }
439               }
440         }
441 
442       if (with_ASCII && !all_ASCII_compatible)
443         {
444           /* assert (first != NULL); */
445           if (second == NULL)
446             second = po_charset_ascii;
447         }
448 
449       if (second != NULL)
450         {
451           /* A conversion is needed.  Warn the user since he hasn't asked
452              for it and might be surprised.  */
453           if (with_UTF8)
454             multiline_warning (xasprintf (_("warning: ")),
455                                xasprintf (_("\
456 Input files contain messages in different encodings, UTF-8 among others.\n\
457 Converting the output to UTF-8.\n\
458 ")));
459           else
460             multiline_warning (xasprintf (_("warning: ")),
461                                xasprintf (_("\
462 Input files contain messages in different encodings, %s and %s among others.\n\
463 Converting the output to UTF-8.\n\
464 To select a different output encoding, use the --to-code option.\n\
465 "), first, second));
466           canon_to_code = po_charset_utf8;
467         }
468       else if (first != NULL && with_ASCII && all_ASCII_compatible)
469         {
470           /* The conversion is a no-op conversion.  Don't warn the user,
471              but still perform the conversion, in order to check that the
472              input was really ASCII.  */
473           canon_to_code = first;
474         }
475       else
476         {
477           /* No conversion needed.  */
478           canon_to_code = NULL;
479         }
480     }
481 
482   /* Now convert the remaining messages to to_code.  */
483   if (canon_to_code != NULL)
484     for (n = 0; n < nfiles; n++)
485       {
486         msgdomain_list_ty *mdlp = mdlps[n];
487         size_t k;
488 
489         for (k = 0; k < mdlp->nitems; k++)
490           if (canon_charsets[n][k] != NULL)
491             /* If the user hasn't given a to_code, don't bother doing a noop
492                conversion that would only replace the charset name in the
493                header entry with its canonical equivalent.  */
494             if (!(to_code == NULL && canon_charsets[n][k] == canon_to_code))
495               if (iconv_message_list (mdlp->item[k]->messages,
496                                       canon_charsets[n][k], canon_to_code,
497                                       files[n]))
498                 {
499                   multiline_error (xstrdup (""),
500                                    xasprintf (_("\
501 Conversion of file %s from %s encoding to %s encoding\n\
502 changes some msgids or msgctxts.\n\
503 Either change all msgids and msgctxts to be pure ASCII, or ensure they are\n\
504 UTF-8 encoded from the beginning, i.e. already in your source code files.\n"),
505                                               files[n], canon_charsets[n][k],
506                                               canon_to_code));
507                   exit (EXIT_FAILURE);
508                 }
509       }
510 
511   /* Fill the resulting messages.  */
512   for (n = 0; n < nfiles; n++)
513     {
514       msgdomain_list_ty *mdlp = mdlps[n];
515       size_t k;
516 
517       for (k = 0; k < mdlp->nitems; k++)
518         {
519           message_list_ty *mlp = mdlp->item[k]->messages;
520 
521           for (j = 0; j < mlp->nitems; j++)
522             {
523               message_ty *mp = mlp->item[j];
524               message_ty *tmp = mp->tmp;
525               size_t i;
526 
527               /* No need to discard unneeded weak translations here;
528                  they have already been filtered out above.  */
529               if (use_first || tmp->used == 1 || tmp->used == -1)
530                 {
531                   /* Copy mp, as only message, into tmp.  */
532                   tmp->msgstr = mp->msgstr;
533                   tmp->msgstr_len = mp->msgstr_len;
534                   tmp->pos = mp->pos;
535                   if (mp->comment)
536                     for (i = 0; i < mp->comment->nitems; i++)
537                       message_comment_append (tmp, mp->comment->item[i]);
538                   if (mp->comment_dot)
539                     for (i = 0; i < mp->comment_dot->nitems; i++)
540                       message_comment_dot_append (tmp,
541                                                   mp->comment_dot->item[i]);
542                   for (i = 0; i < mp->filepos_count; i++)
543                     message_comment_filepos (tmp, mp->filepos[i].file_name,
544                                              mp->filepos[i].line_number);
545                   tmp->is_fuzzy = mp->is_fuzzy;
546                   for (i = 0; i < NFORMATS; i++)
547                     tmp->is_format[i] = mp->is_format[i];
548                   tmp->range = mp->range;
549                   tmp->do_wrap = mp->do_wrap;
550                   for (i = 0; i < NSYNTAXCHECKS; i++)
551                     tmp->do_syntax_check[i] = mp->do_syntax_check[i];
552                   tmp->prev_msgctxt = mp->prev_msgctxt;
553                   tmp->prev_msgid = mp->prev_msgid;
554                   tmp->prev_msgid_plural = mp->prev_msgid_plural;
555                   tmp->obsolete = mp->obsolete;
556                 }
557               else if (msgcomm_mode)
558                 {
559                   /* Copy mp, as only message, into tmp.  */
560                   if (tmp->msgstr == NULL)
561                     {
562                       tmp->msgstr = mp->msgstr;
563                       tmp->msgstr_len = mp->msgstr_len;
564                       tmp->pos = mp->pos;
565                       tmp->is_fuzzy = mp->is_fuzzy;
566                       tmp->prev_msgctxt = mp->prev_msgctxt;
567                       tmp->prev_msgid = mp->prev_msgid;
568                       tmp->prev_msgid_plural = mp->prev_msgid_plural;
569                     }
570                   if (mp->comment && tmp->comment == NULL)
571                     for (i = 0; i < mp->comment->nitems; i++)
572                       message_comment_append (tmp, mp->comment->item[i]);
573                   if (mp->comment_dot && tmp->comment_dot == NULL)
574                     for (i = 0; i < mp->comment_dot->nitems; i++)
575                       message_comment_dot_append (tmp,
576                                                   mp->comment_dot->item[i]);
577                   for (i = 0; i < mp->filepos_count; i++)
578                     message_comment_filepos (tmp, mp->filepos[i].file_name,
579                                              mp->filepos[i].line_number);
580                   for (i = 0; i < NFORMATS; i++)
581                     if (tmp->is_format[i] == undecided)
582                       tmp->is_format[i] = mp->is_format[i];
583                   if (tmp->range.min == - INT_MAX
584                       && tmp->range.max == - INT_MAX)
585                     tmp->range = mp->range;
586                   else if (has_range_p (mp->range) && has_range_p (tmp->range))
587                     {
588                       if (mp->range.min < tmp->range.min)
589                         tmp->range.min = mp->range.min;
590                       if (mp->range.max > tmp->range.max)
591                         tmp->range.max = mp->range.max;
592                     }
593                   else
594                     {
595                       tmp->range.min = -1;
596                       tmp->range.max = -1;
597                     }
598                   if (tmp->do_wrap == undecided)
599                     tmp->do_wrap = mp->do_wrap;
600                   for (i = 0; i < NSYNTAXCHECKS; i++)
601                     if (tmp->do_syntax_check[i] == undecided)
602                       tmp->do_syntax_check[i] = mp->do_syntax_check[i];
603                   tmp->obsolete = false;
604                 }
605               else
606                 {
607                   /* Copy mp, among others, into tmp.  */
608                   char *id = xasprintf ("#-#-#-#-#  %s  #-#-#-#-#",
609                                         identifications[n][k]);
610                   size_t nbytes;
611 
612                   if (tmp->alternative_count == 0)
613                     tmp->pos = mp->pos;
614 
615                   i = tmp->alternative_count;
616                   nbytes = (i + 1) * sizeof (struct altstr);
617                   tmp->alternative = xrealloc (tmp->alternative, nbytes);
618                   tmp->alternative[i].msgstr = mp->msgstr;
619                   tmp->alternative[i].msgstr_len = mp->msgstr_len;
620                   tmp->alternative[i].msgstr_end =
621                     tmp->alternative[i].msgstr + tmp->alternative[i].msgstr_len;
622                   tmp->alternative[i].comment = mp->comment;
623                   tmp->alternative[i].comment_dot = mp->comment_dot;
624                   tmp->alternative[i].id = id;
625                   tmp->alternative_count = i + 1;
626 
627                   for (i = 0; i < mp->filepos_count; i++)
628                     message_comment_filepos (tmp, mp->filepos[i].file_name,
629                                              mp->filepos[i].line_number);
630                   if (!mp->is_fuzzy)
631                     tmp->is_fuzzy = false;
632                   for (i = 0; i < NFORMATS; i++)
633                     if (mp->is_format[i] == yes)
634                       tmp->is_format[i] = yes;
635                     else if (mp->is_format[i] == no
636                              && tmp->is_format[i] == undecided)
637                       tmp->is_format[i] = no;
638                   if (tmp->range.min == - INT_MAX
639                       && tmp->range.max == - INT_MAX)
640                     tmp->range = mp->range;
641                   else if (has_range_p (mp->range) && has_range_p (tmp->range))
642                     {
643                       if (mp->range.min < tmp->range.min)
644                         tmp->range.min = mp->range.min;
645                       if (mp->range.max > tmp->range.max)
646                         tmp->range.max = mp->range.max;
647                     }
648                   else
649                     {
650                       tmp->range.min = -1;
651                       tmp->range.max = -1;
652                     }
653                   if (mp->do_wrap == no)
654                     tmp->do_wrap = no;
655                   for (i = 0; i < NSYNTAXCHECKS; i++)
656                     if (mp->do_syntax_check[i] == yes)
657                       tmp->do_syntax_check[i] = yes;
658                     else if (mp->do_syntax_check[i] == no
659                              && tmp->do_syntax_check[i] == undecided)
660                       tmp->do_syntax_check[i] = no;
661                   /* Don't fill tmp->prev_msgid in this case.  */
662                   if (!mp->obsolete)
663                     tmp->obsolete = false;
664                 }
665             }
666         }
667     }
668   {
669     size_t k;
670 
671     for (k = 0; k < total_mdlp->nitems; k++)
672       {
673         message_list_ty *mlp = total_mdlp->item[k]->messages;
674 
675         for (j = 0; j < mlp->nitems; j++)
676           {
677             message_ty *tmp = mlp->item[j];
678 
679             if (tmp->alternative_count > 0)
680               {
681                 /* Test whether all alternative translations are equal.  */
682                 struct altstr *first = &tmp->alternative[0];
683                 size_t i;
684 
685                 for (i = 0; i < tmp->alternative_count; i++)
686                   if (!(tmp->alternative[i].msgstr_len == first->msgstr_len
687                         && memcmp (tmp->alternative[i].msgstr, first->msgstr,
688                                    first->msgstr_len) == 0))
689                     break;
690 
691                 if (i == tmp->alternative_count)
692                   {
693                     /* All alternatives are equal.  */
694                     tmp->msgstr = first->msgstr;
695                     tmp->msgstr_len = first->msgstr_len;
696                   }
697                 else
698                   {
699                     /* Concatenate the alternative msgstrs into a single one,
700                        separated by markers.  */
701                     size_t len;
702                     const char *p;
703                     const char *p_end;
704                     char *new_msgstr;
705                     char *np;
706 
707                     len = 0;
708                     for (i = 0; i < tmp->alternative_count; i++)
709                       {
710                         size_t id_len = strlen (tmp->alternative[i].id);
711 
712                         len += tmp->alternative[i].msgstr_len;
713 
714                         p = tmp->alternative[i].msgstr;
715                         p_end = tmp->alternative[i].msgstr_end;
716                         for (; p < p_end; p += strlen (p) + 1)
717                           len += id_len + 2;
718                       }
719 
720                     new_msgstr = XNMALLOC (len, char);
721                     np = new_msgstr;
722                     for (;;)
723                       {
724                         /* Test whether there's one more plural form to
725                            process.  */
726                         for (i = 0; i < tmp->alternative_count; i++)
727                           if (tmp->alternative[i].msgstr
728                               < tmp->alternative[i].msgstr_end)
729                             break;
730                         if (i == tmp->alternative_count)
731                           break;
732 
733                         /* Process next plural form.  */
734                         for (i = 0; i < tmp->alternative_count; i++)
735                           if (tmp->alternative[i].msgstr
736                               < tmp->alternative[i].msgstr_end)
737                             {
738                               if (np > new_msgstr && np[-1] != '\0'
739                                   && np[-1] != '\n')
740                                 *np++ = '\n';
741 
742                               len = strlen (tmp->alternative[i].id);
743                               memcpy (np, tmp->alternative[i].id, len);
744                               np += len;
745                               *np++ = '\n';
746 
747                               len = strlen (tmp->alternative[i].msgstr);
748                               memcpy (np, tmp->alternative[i].msgstr, len);
749                               np += len;
750                               tmp->alternative[i].msgstr += len + 1;
751                             }
752 
753                         /* Plural forms are separated by NUL bytes.  */
754                         *np++ = '\0';
755                       }
756                     tmp->msgstr = new_msgstr;
757                     tmp->msgstr_len = np - new_msgstr;
758 
759                     tmp->is_fuzzy = true;
760                   }
761 
762                 /* Test whether all alternative comments are equal.  */
763                 for (i = 0; i < tmp->alternative_count; i++)
764                   if (tmp->alternative[i].comment == NULL
765                       || !string_list_equal (tmp->alternative[i].comment,
766                                              first->comment))
767                     break;
768 
769                 if (i == tmp->alternative_count)
770                   /* All alternatives are equal.  */
771                   tmp->comment = first->comment;
772                 else
773                   /* Concatenate the alternative comments into a single one,
774                      separated by markers.  */
775                   for (i = 0; i < tmp->alternative_count; i++)
776                     {
777                       string_list_ty *slp = tmp->alternative[i].comment;
778 
779                       if (slp != NULL)
780                         {
781                           size_t l;
782 
783                           message_comment_append (tmp, tmp->alternative[i].id);
784                           for (l = 0; l < slp->nitems; l++)
785                             message_comment_append (tmp, slp->item[l]);
786                         }
787                     }
788 
789                 /* Test whether all alternative dot comments are equal.  */
790                 for (i = 0; i < tmp->alternative_count; i++)
791                   if (tmp->alternative[i].comment_dot == NULL
792                       || !string_list_equal (tmp->alternative[i].comment_dot,
793                                              first->comment_dot))
794                     break;
795 
796                 if (i == tmp->alternative_count)
797                   /* All alternatives are equal.  */
798                   tmp->comment_dot = first->comment_dot;
799                 else
800                   /* Concatenate the alternative dot comments into a single one,
801                      separated by markers.  */
802                   for (i = 0; i < tmp->alternative_count; i++)
803                     {
804                       string_list_ty *slp = tmp->alternative[i].comment_dot;
805 
806                       if (slp != NULL)
807                         {
808                           size_t l;
809 
810                           message_comment_dot_append (tmp,
811                                                       tmp->alternative[i].id);
812                           for (l = 0; l < slp->nitems; l++)
813                             message_comment_dot_append (tmp, slp->item[l]);
814                         }
815                     }
816               }
817           }
818       }
819   }
820 
821   return total_mdlp;
822 }
823