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 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 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 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 * 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