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