Imported Upstream version 0.19.7
[platform/upstream/gettext.git] / gettext-tools / src / msgl-cat.c
1 /* Message list concatenation and duplicate handling.
2    Copyright (C) 2001-2003, 2005-2008, 2012, 2015 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 <http://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.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
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
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
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 *
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                                          _("\
179 present charset \"%s\" is not a portable encoding name"),
180                                          charset);
181                               }
182
183                             freea (charset);
184
185                             if (canon_from_code == NULL)
186                               canon_from_code = canon_charset;
187                             else if (canon_from_code != canon_charset)
188                               error (EXIT_FAILURE, 0,
189                                      _("\
190 two different charsets \"%s\" and \"%s\" in input file"),
191                                      canon_from_code, canon_charset);
192                           }
193                       }
194                   }
195               if (canon_from_code == NULL)
196                 {
197                   if (is_ascii_message_list (mlp))
198                     canon_from_code = po_charset_ascii;
199                   else if (mdlp->encoding != NULL)
200                     canon_from_code = mdlp->encoding;
201                   else
202                     {
203                       if (k == 0)
204                         error (EXIT_FAILURE, 0, _("\
205 input file '%s' doesn't contain a header entry with a charset specification"),
206                                files[n]);
207                       else
208                         error (EXIT_FAILURE, 0, _("\
209 domain \"%s\" in input file '%s' doesn't contain a header entry with a charset specification"),
210                                mdlp->item[k]->domain, files[n]);
211                     }
212                 }
213             }
214           canon_charsets[n][k] = canon_from_code;
215         }
216     }
217
218   /* Determine textual identifications of each file/domain combination.  */
219   identifications = XNMALLOC (nfiles, const char **);
220   for (n = 0; n < nfiles; n++)
221     {
222       const char *filename = basename (files[n]);
223       msgdomain_list_ty *mdlp = mdlps[n];
224       size_t k;
225
226       identifications[n] = XNMALLOC (mdlp->nitems, const char *);
227       for (k = 0; k < mdlp->nitems; k++)
228         {
229           const char *domain = mdlp->item[k]->domain;
230           message_list_ty *mlp = mdlp->item[k]->messages;
231           char *project_id = NULL;
232
233           for (j = 0; j < mlp->nitems; j++)
234             if (is_header (mlp->item[j]) && !mlp->item[j]->obsolete)
235               {
236                 const char *header = mlp->item[j]->msgstr;
237
238                 if (header != NULL)
239                   {
240                     const char *cp = c_strstr (header, "Project-Id-Version:");
241
242                     if (cp != NULL)
243                       {
244                         const char *endp;
245
246                         cp += sizeof ("Project-Id-Version:") - 1;
247
248                         endp = strchr (cp, '\n');
249                         if (endp == NULL)
250                           endp = cp + strlen (cp);
251
252                         while (cp < endp && *cp == ' ')
253                           cp++;
254
255                         if (cp < endp)
256                           {
257                             size_t len = endp - cp;
258                             project_id = XNMALLOC (len + 1, char);
259                             memcpy (project_id, cp, len);
260                             project_id[len] = '\0';
261                           }
262                         break;
263                       }
264                   }
265               }
266
267           identifications[n][k] =
268             (project_id != NULL
269              ? (k > 0 ? xasprintf ("%s:%s (%s)", filename, domain, project_id)
270                       : xasprintf ("%s (%s)", filename, project_id))
271              : (k > 0 ? xasprintf ("%s:%s", filename, domain)
272                       : xasprintf ("%s", filename)));
273         }
274     }
275
276   /* Create list of resulting messages, but don't fill it.  Only count
277      the number of translations for each message.
278      If for a message, there is at least one non-fuzzy, non-empty translation,
279      use only the non-fuzzy, non-empty translations.  Otherwise use the
280      fuzzy or empty translations as well.  */
281   total_mdlp = msgdomain_list_alloc (true);
282   for (n = 0; n < nfiles; n++)
283     {
284       msgdomain_list_ty *mdlp = mdlps[n];
285       size_t k;
286
287       for (k = 0; k < mdlp->nitems; k++)
288         {
289           const char *domain = mdlp->item[k]->domain;
290           message_list_ty *mlp = mdlp->item[k]->messages;
291           message_list_ty *total_mlp;
292
293           total_mlp = msgdomain_list_sublist (total_mdlp, domain, true);
294
295           for (j = 0; j < mlp->nitems; j++)
296             {
297               message_ty *mp = mlp->item[j];
298               message_ty *tmp;
299               size_t i;
300
301               tmp = message_list_search (total_mlp, mp->msgctxt, mp->msgid);
302               if (tmp == NULL)
303                 {
304                   tmp = message_alloc (mp->msgctxt, mp->msgid, mp->msgid_plural,
305                                        NULL, 0, &mp->pos);
306                   tmp->is_fuzzy = true; /* may be set to false later */
307                   for (i = 0; i < NFORMATS; i++)
308                     tmp->is_format[i] = undecided; /* may be set to yes/no later */
309                   tmp->range.min = - INT_MAX;
310                   tmp->range.max = - INT_MAX;
311                   tmp->do_wrap = yes; /* may be set to no later */
312                   for (i = 0; i < NSYNTAXCHECKS; i++)
313                     tmp->do_syntax_check[i] = undecided; /* may be set to yes/no later */
314                   tmp->obsolete = true; /* may be set to false later */
315                   tmp->alternative_count = 0;
316                   tmp->alternative = NULL;
317                   message_list_append (total_mlp, tmp);
318                 }
319
320               if (!msgcomm_mode
321                   && ((!is_header (mp) && mp->is_fuzzy)
322                       || mp->msgstr[0] == '\0'))
323                 /* Weak translation.  Counted as negative tmp->used.  */
324                 {
325                   if (tmp->used <= 0)
326                     tmp->used--;
327                 }
328               else
329                 /* Good translation.  Counted as positive tmp->used.  */
330                 {
331                   if (tmp->used < 0)
332                     tmp->used = 0;
333                   tmp->used++;
334                 }
335               mp->tmp = tmp;
336             }
337         }
338     }
339
340   /* Remove messages that are not used and need not be converted.  */
341   for (n = 0; n < nfiles; n++)
342     {
343       msgdomain_list_ty *mdlp = mdlps[n];
344       size_t k;
345
346       for (k = 0; k < mdlp->nitems; k++)
347         {
348           message_list_ty *mlp = mdlp->item[k]->messages;
349
350           message_list_remove_if_not (mlp,
351                                       use_first
352                                       ? is_message_first_needed
353                                       : is_message_needed);
354
355           /* If no messages are remaining, drop the charset.  */
356           if (mlp->nitems == 0)
357             canon_charsets[n][k] = NULL;
358         }
359     }
360   {
361     size_t k;
362
363     for (k = 0; k < total_mdlp->nitems; k++)
364       {
365         message_list_ty *mlp = total_mdlp->item[k]->messages;
366
367         message_list_remove_if_not (mlp, is_message_selected);
368       }
369   }
370
371   /* Determine the common known a-priori encoding, if any.  */
372   if (nfiles > 0)
373     {
374       bool all_same_encoding = true;
375
376       for (n = 1; n < nfiles; n++)
377         if (mdlps[n]->encoding != mdlps[0]->encoding)
378           {
379             all_same_encoding = false;
380             break;
381           }
382
383       if (all_same_encoding)
384         total_mdlp->encoding = mdlps[0]->encoding;
385     }
386
387   /* Determine the target encoding for the remaining messages.  */
388   if (to_code != NULL)
389     {
390       /* Canonicalize target encoding.  */
391       canon_to_code = po_charset_canonicalize (to_code);
392       if (canon_to_code == NULL)
393         error (EXIT_FAILURE, 0,
394                _("target charset \"%s\" is not a portable encoding name."),
395                to_code);
396     }
397   else
398     {
399       /* No target encoding was specified.  Test whether the messages are
400          all in a single encoding.  If so, conversion is not needed.  */
401       const char *first = NULL;
402       const char *second = NULL;
403       bool with_ASCII = false;
404       bool with_UTF8 = false;
405       bool all_ASCII_compatible = true;
406
407       for (n = 0; n < nfiles; n++)
408         {
409           msgdomain_list_ty *mdlp = mdlps[n];
410           size_t k;
411
412           for (k = 0; k < mdlp->nitems; k++)
413             if (canon_charsets[n][k] != NULL)
414               {
415                 if (canon_charsets[n][k] == po_charset_ascii)
416                   with_ASCII = true;
417                 else
418                   {
419                     if (first == NULL)
420                       first = canon_charsets[n][k];
421                     else if (canon_charsets[n][k] != first && second == NULL)
422                       second = canon_charsets[n][k];
423
424                     if (strcmp (canon_charsets[n][k], "UTF-8") == 0)
425                       with_UTF8 = true;
426
427                     if (!po_charset_ascii_compatible (canon_charsets[n][k]))
428                       all_ASCII_compatible = false;
429                   }
430               }
431         }
432
433       if (with_ASCII && !all_ASCII_compatible)
434         {
435           /* assert (first != NULL); */
436           if (second == NULL)
437             second = po_charset_ascii;
438         }
439
440       if (second != NULL)
441         {
442           /* A conversion is needed.  Warn the user since he hasn't asked
443              for it and might be surprised.  */
444           if (with_UTF8)
445             multiline_warning (xasprintf (_("warning: ")),
446                                xasprintf (_("\
447 Input files contain messages in different encodings, UTF-8 among others.\n\
448 Converting the output to UTF-8.\n\
449 ")));
450           else
451             multiline_warning (xasprintf (_("warning: ")),
452                                xasprintf (_("\
453 Input files contain messages in different encodings, %s and %s among others.\n\
454 Converting the output to UTF-8.\n\
455 To select a different output encoding, use the --to-code option.\n\
456 "), first, second));
457           canon_to_code = po_charset_utf8;
458         }
459       else if (first != NULL && with_ASCII && all_ASCII_compatible)
460         {
461           /* The conversion is a no-op conversion.  Don't warn the user,
462              but still perform the conversion, in order to check that the
463              input was really ASCII.  */
464           canon_to_code = first;
465         }
466       else
467         {
468           /* No conversion needed.  */
469           canon_to_code = NULL;
470         }
471     }
472
473   /* Now convert the remaining messages to to_code.  */
474   if (canon_to_code != NULL)
475     for (n = 0; n < nfiles; n++)
476       {
477         msgdomain_list_ty *mdlp = mdlps[n];
478         size_t k;
479
480         for (k = 0; k < mdlp->nitems; k++)
481           if (canon_charsets[n][k] != NULL)
482             /* If the user hasn't given a to_code, don't bother doing a noop
483                conversion that would only replace the charset name in the
484                header entry with its canonical equivalent.  */
485             if (!(to_code == NULL && canon_charsets[n][k] == canon_to_code))
486               if (iconv_message_list (mdlp->item[k]->messages,
487                                       canon_charsets[n][k], canon_to_code,
488                                       files[n]))
489                 {
490                   multiline_error (xstrdup (""),
491                                    xasprintf (_("\
492 Conversion of file %s from %s encoding to %s encoding\n\
493 changes some msgids or msgctxts.\n\
494 Either change all msgids and msgctxts to be pure ASCII, or ensure they are\n\
495 UTF-8 encoded from the beginning, i.e. already in your source code files.\n"),
496                                               files[n], canon_charsets[n][k],
497                                               canon_to_code));
498                   exit (EXIT_FAILURE);
499                 }
500       }
501
502   /* Fill the resulting messages.  */
503   for (n = 0; n < nfiles; n++)
504     {
505       msgdomain_list_ty *mdlp = mdlps[n];
506       size_t k;
507
508       for (k = 0; k < mdlp->nitems; k++)
509         {
510           message_list_ty *mlp = mdlp->item[k]->messages;
511
512           for (j = 0; j < mlp->nitems; j++)
513             {
514               message_ty *mp = mlp->item[j];
515               message_ty *tmp = mp->tmp;
516               size_t i;
517
518               /* No need to discard unneeded weak translations here;
519                  they have already been filtered out above.  */
520               if (use_first || tmp->used == 1 || tmp->used == -1)
521                 {
522                   /* Copy mp, as only message, into tmp.  */
523                   tmp->msgstr = mp->msgstr;
524                   tmp->msgstr_len = mp->msgstr_len;
525                   tmp->pos = mp->pos;
526                   if (mp->comment)
527                     for (i = 0; i < mp->comment->nitems; i++)
528                       message_comment_append (tmp, mp->comment->item[i]);
529                   if (mp->comment_dot)
530                     for (i = 0; i < mp->comment_dot->nitems; i++)
531                       message_comment_dot_append (tmp,
532                                                   mp->comment_dot->item[i]);
533                   for (i = 0; i < mp->filepos_count; i++)
534                     message_comment_filepos (tmp, mp->filepos[i].file_name,
535                                              mp->filepos[i].line_number);
536                   tmp->is_fuzzy = mp->is_fuzzy;
537                   for (i = 0; i < NFORMATS; i++)
538                     tmp->is_format[i] = mp->is_format[i];
539                   tmp->range = mp->range;
540                   tmp->do_wrap = mp->do_wrap;
541                   for (i = 0; i < NSYNTAXCHECKS; i++)
542                     tmp->do_syntax_check[i] = mp->do_syntax_check[i];
543                   tmp->prev_msgctxt = mp->prev_msgctxt;
544                   tmp->prev_msgid = mp->prev_msgid;
545                   tmp->prev_msgid_plural = mp->prev_msgid_plural;
546                   tmp->obsolete = mp->obsolete;
547                 }
548               else if (msgcomm_mode)
549                 {
550                   /* Copy mp, as only message, into tmp.  */
551                   if (tmp->msgstr == NULL)
552                     {
553                       tmp->msgstr = mp->msgstr;
554                       tmp->msgstr_len = mp->msgstr_len;
555                       tmp->pos = mp->pos;
556                       tmp->is_fuzzy = mp->is_fuzzy;
557                       tmp->prev_msgctxt = mp->prev_msgctxt;
558                       tmp->prev_msgid = mp->prev_msgid;
559                       tmp->prev_msgid_plural = mp->prev_msgid_plural;
560                     }
561                   if (mp->comment && tmp->comment == NULL)
562                     for (i = 0; i < mp->comment->nitems; i++)
563                       message_comment_append (tmp, mp->comment->item[i]);
564                   if (mp->comment_dot && tmp->comment_dot == NULL)
565                     for (i = 0; i < mp->comment_dot->nitems; i++)
566                       message_comment_dot_append (tmp,
567                                                   mp->comment_dot->item[i]);
568                   for (i = 0; i < mp->filepos_count; i++)
569                     message_comment_filepos (tmp, mp->filepos[i].file_name,
570                                              mp->filepos[i].line_number);
571                   for (i = 0; i < NFORMATS; i++)
572                     if (tmp->is_format[i] == undecided)
573                       tmp->is_format[i] = mp->is_format[i];
574                   if (tmp->range.min == - INT_MAX
575                       && tmp->range.max == - INT_MAX)
576                     tmp->range = mp->range;
577                   else if (has_range_p (mp->range) && has_range_p (tmp->range))
578                     {
579                       if (mp->range.min < tmp->range.min)
580                         tmp->range.min = mp->range.min;
581                       if (mp->range.max > tmp->range.max)
582                         tmp->range.max = mp->range.max;
583                     }
584                   else
585                     {
586                       tmp->range.min = -1;
587                       tmp->range.max = -1;
588                     }
589                   if (tmp->do_wrap == undecided)
590                     tmp->do_wrap = mp->do_wrap;
591                   for (i = 0; i < NSYNTAXCHECKS; i++)
592                     if (tmp->do_syntax_check[i] == undecided)
593                       tmp->do_syntax_check[i] = mp->do_syntax_check[i];
594                   tmp->obsolete = false;
595                 }
596               else
597                 {
598                   /* Copy mp, among others, into tmp.  */
599                   char *id = xasprintf ("#-#-#-#-#  %s  #-#-#-#-#",
600                                         identifications[n][k]);
601                   size_t nbytes;
602
603                   if (tmp->alternative_count == 0)
604                     tmp->pos = mp->pos;
605
606                   i = tmp->alternative_count;
607                   nbytes = (i + 1) * sizeof (struct altstr);
608                   tmp->alternative = xrealloc (tmp->alternative, nbytes);
609                   tmp->alternative[i].msgstr = mp->msgstr;
610                   tmp->alternative[i].msgstr_len = mp->msgstr_len;
611                   tmp->alternative[i].msgstr_end =
612                     tmp->alternative[i].msgstr + tmp->alternative[i].msgstr_len;
613                   tmp->alternative[i].comment = mp->comment;
614                   tmp->alternative[i].comment_dot = mp->comment_dot;
615                   tmp->alternative[i].id = id;
616                   tmp->alternative_count = i + 1;
617
618                   for (i = 0; i < mp->filepos_count; i++)
619                     message_comment_filepos (tmp, mp->filepos[i].file_name,
620                                              mp->filepos[i].line_number);
621                   if (!mp->is_fuzzy)
622                     tmp->is_fuzzy = false;
623                   for (i = 0; i < NFORMATS; i++)
624                     if (mp->is_format[i] == yes)
625                       tmp->is_format[i] = yes;
626                     else if (mp->is_format[i] == no
627                              && tmp->is_format[i] == undecided)
628                       tmp->is_format[i] = no;
629                   if (tmp->range.min == - INT_MAX
630                       && tmp->range.max == - INT_MAX)
631                     tmp->range = mp->range;
632                   else if (has_range_p (mp->range) && has_range_p (tmp->range))
633                     {
634                       if (mp->range.min < tmp->range.min)
635                         tmp->range.min = mp->range.min;
636                       if (mp->range.max > tmp->range.max)
637                         tmp->range.max = mp->range.max;
638                     }
639                   else
640                     {
641                       tmp->range.min = -1;
642                       tmp->range.max = -1;
643                     }
644                   if (mp->do_wrap == no)
645                     tmp->do_wrap = no;
646                   for (i = 0; i < NSYNTAXCHECKS; i++)
647                     if (mp->do_syntax_check[i] == yes)
648                       tmp->do_syntax_check[i] = yes;
649                     else if (mp->do_syntax_check[i] == no
650                              && tmp->do_syntax_check[i] == undecided)
651                       tmp->do_syntax_check[i] = no;
652                   /* Don't fill tmp->prev_msgid in this case.  */
653                   if (!mp->obsolete)
654                     tmp->obsolete = false;
655                 }
656             }
657         }
658     }
659   {
660     size_t k;
661
662     for (k = 0; k < total_mdlp->nitems; k++)
663       {
664         message_list_ty *mlp = total_mdlp->item[k]->messages;
665
666         for (j = 0; j < mlp->nitems; j++)
667           {
668             message_ty *tmp = mlp->item[j];
669
670             if (tmp->alternative_count > 0)
671               {
672                 /* Test whether all alternative translations are equal.  */
673                 struct altstr *first = &tmp->alternative[0];
674                 size_t i;
675
676                 for (i = 0; i < tmp->alternative_count; i++)
677                   if (!(tmp->alternative[i].msgstr_len == first->msgstr_len
678                         && memcmp (tmp->alternative[i].msgstr, first->msgstr,
679                                    first->msgstr_len) == 0))
680                     break;
681
682                 if (i == tmp->alternative_count)
683                   {
684                     /* All alternatives are equal.  */
685                     tmp->msgstr = first->msgstr;
686                     tmp->msgstr_len = first->msgstr_len;
687                   }
688                 else
689                   {
690                     /* Concatenate the alternative msgstrs into a single one,
691                        separated by markers.  */
692                     size_t len;
693                     const char *p;
694                     const char *p_end;
695                     char *new_msgstr;
696                     char *np;
697
698                     len = 0;
699                     for (i = 0; i < tmp->alternative_count; i++)
700                       {
701                         size_t id_len = strlen (tmp->alternative[i].id);
702
703                         len += tmp->alternative[i].msgstr_len;
704
705                         p = tmp->alternative[i].msgstr;
706                         p_end = tmp->alternative[i].msgstr_end;
707                         for (; p < p_end; p += strlen (p) + 1)
708                           len += id_len + 2;
709                       }
710
711                     new_msgstr = XNMALLOC (len, char);
712                     np = new_msgstr;
713                     for (;;)
714                       {
715                         /* Test whether there's one more plural form to
716                            process.  */
717                         for (i = 0; i < tmp->alternative_count; i++)
718                           if (tmp->alternative[i].msgstr
719                               < tmp->alternative[i].msgstr_end)
720                             break;
721                         if (i == tmp->alternative_count)
722                           break;
723
724                         /* Process next plural form.  */
725                         for (i = 0; i < tmp->alternative_count; i++)
726                           if (tmp->alternative[i].msgstr
727                               < tmp->alternative[i].msgstr_end)
728                             {
729                               if (np > new_msgstr && np[-1] != '\0'
730                                   && np[-1] != '\n')
731                                 *np++ = '\n';
732
733                               len = strlen (tmp->alternative[i].id);
734                               memcpy (np, tmp->alternative[i].id, len);
735                               np += len;
736                               *np++ = '\n';
737
738                               len = strlen (tmp->alternative[i].msgstr);
739                               memcpy (np, tmp->alternative[i].msgstr, len);
740                               np += len;
741                               tmp->alternative[i].msgstr += len + 1;
742                             }
743
744                         /* Plural forms are separated by NUL bytes.  */
745                         *np++ = '\0';
746                       }
747                     tmp->msgstr = new_msgstr;
748                     tmp->msgstr_len = np - new_msgstr;
749
750                     tmp->is_fuzzy = true;
751                   }
752
753                 /* Test whether all alternative comments are equal.  */
754                 for (i = 0; i < tmp->alternative_count; i++)
755                   if (tmp->alternative[i].comment == NULL
756                       || !string_list_equal (tmp->alternative[i].comment,
757                                              first->comment))
758                     break;
759
760                 if (i == tmp->alternative_count)
761                   /* All alternatives are equal.  */
762                   tmp->comment = first->comment;
763                 else
764                   /* Concatenate the alternative comments into a single one,
765                      separated by markers.  */
766                   for (i = 0; i < tmp->alternative_count; i++)
767                     {
768                       string_list_ty *slp = tmp->alternative[i].comment;
769
770                       if (slp != NULL)
771                         {
772                           size_t l;
773
774                           message_comment_append (tmp, tmp->alternative[i].id);
775                           for (l = 0; l < slp->nitems; l++)
776                             message_comment_append (tmp, slp->item[l]);
777                         }
778                     }
779
780                 /* Test whether all alternative dot comments are equal.  */
781                 for (i = 0; i < tmp->alternative_count; i++)
782                   if (tmp->alternative[i].comment_dot == NULL
783                       || !string_list_equal (tmp->alternative[i].comment_dot,
784                                              first->comment_dot))
785                     break;
786
787                 if (i == tmp->alternative_count)
788                   /* All alternatives are equal.  */
789                   tmp->comment_dot = first->comment_dot;
790                 else
791                   /* Concatenate the alternative dot comments into a single one,
792                      separated by markers.  */
793                   for (i = 0; i < tmp->alternative_count; i++)
794                     {
795                       string_list_ty *slp = tmp->alternative[i].comment_dot;
796
797                       if (slp != NULL)
798                         {
799                           size_t l;
800
801                           message_comment_dot_append (tmp,
802                                                       tmp->alternative[i].id);
803                           for (l = 0; l < slp->nitems; l++)
804                             message_comment_dot_append (tmp, slp->item[l]);
805                         }
806                     }
807               }
808           }
809       }
810   }
811
812   return total_mdlp;
813 }