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