(root)/
gettext-0.22.4/
gettext-tools/
src/
msgl-cat.c
       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  }