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