(root)/
gettext-0.22.4/
gettext-tools/
src/
write-catalog.c
       1  /* GNU gettext - internationalization aids
       2     Copyright (C) 1995-1998, 2000-2008, 2012, 2019-2020 Free Software
       3     Foundation, Inc.
       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  #ifdef HAVE_CONFIG_H
      19  # include <config.h>
      20  #endif
      21  
      22  /* Specification.  */
      23  #include "write-catalog.h"
      24  
      25  #include <errno.h>
      26  #include <fcntl.h>
      27  #include <limits.h>
      28  #include <stdio.h>
      29  #include <stdlib.h>
      30  #include <string.h>
      31  
      32  #include <unistd.h>
      33  #ifndef STDOUT_FILENO
      34  # define STDOUT_FILENO 1
      35  #endif
      36  
      37  #include <textstyle.h>
      38  
      39  #include "fwriteerror.h"
      40  #include "error-progname.h"
      41  #include "xvasprintf.h"
      42  #include "po-xerror.h"
      43  #include "gettext.h"
      44  
      45  /* Our regular abbreviation.  */
      46  #define _(str) gettext (str)
      47  
      48  /* When compiled in src, enable color support.
      49     When compiled in libgettextpo, don't enable color support.  */
      50  #ifdef GETTEXTDATADIR
      51  
      52  # define ENABLE_COLOR 1
      53  
      54  # include "relocatable.h"
      55  # include "po-charset.h"
      56  # include "msgl-iconv.h"
      57  
      58  # define GETTEXTSTYLESDIR  GETTEXTDATADIR "/styles"
      59  
      60  #endif
      61  
      62  
      63  /* =========== Some parameters for use by 'msgdomain_list_print'. ========== */
      64  
      65  
      66  /* This variable controls the page width when printing messages.
      67     Defaults to PAGE_WIDTH if not set.  Zero (0) given to message_page_-
      68     width_set will result in no wrapping being performed.  */
      69  static size_t page_width = PAGE_WIDTH;
      70  
      71  void
      72  message_page_width_set (size_t n)
      73  {
      74    if (n == 0)
      75      {
      76        page_width = INT_MAX;
      77        return;
      78      }
      79  
      80    if (n < 20)
      81      n = 20;
      82  
      83    page_width = n;
      84  }
      85  
      86  
      87  /* ======================== msgdomain_list_print() ======================== */
      88  
      89  
      90  void
      91  msgdomain_list_print (msgdomain_list_ty *mdlp, const char *filename,
      92                        catalog_output_format_ty output_syntax,
      93                        bool force, bool debug)
      94  {
      95    bool to_stdout;
      96  
      97    /* We will not write anything if, for every domain, we have no message
      98       or only the header entry.  */
      99    if (!force)
     100      {
     101        bool found_nonempty = false;
     102        size_t k;
     103  
     104        for (k = 0; k < mdlp->nitems; k++)
     105          {
     106            message_list_ty *mlp = mdlp->item[k]->messages;
     107  
     108            if (!(mlp->nitems == 0
     109                  || (mlp->nitems == 1 && is_header (mlp->item[0]))))
     110              {
     111                found_nonempty = true;
     112                break;
     113              }
     114          }
     115  
     116        if (!found_nonempty)
     117          return;
     118      }
     119  
     120    /* Check whether the output format can accommodate all messages.  */
     121    if (!output_syntax->supports_multiple_domains && mdlp->nitems > 1)
     122      {
     123        if (output_syntax->alternative_is_po)
     124          po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
     125                     _("Cannot output multiple translation domains into a single file with the specified output format. Try using PO file syntax instead."));
     126        else
     127          po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
     128                     _("Cannot output multiple translation domains into a single file with the specified output format."));
     129      }
     130    else
     131      {
     132        if (!output_syntax->supports_contexts)
     133          {
     134            const lex_pos_ty *has_context;
     135            size_t k;
     136  
     137            has_context = NULL;
     138            for (k = 0; k < mdlp->nitems; k++)
     139              {
     140                message_list_ty *mlp = mdlp->item[k]->messages;
     141                size_t j;
     142  
     143                for (j = 0; j < mlp->nitems; j++)
     144                  {
     145                    message_ty *mp = mlp->item[j];
     146  
     147                    if (mp->msgctxt != NULL)
     148                      {
     149                        has_context = &mp->pos;
     150                        break;
     151                      }
     152                  }
     153              }
     154  
     155            if (has_context != NULL)
     156              {
     157                error_with_progname = false;
     158                po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
     159                           has_context->file_name, has_context->line_number,
     160                           (size_t)(-1), false,
     161                           _("message catalog has context dependent translations, but the output format does not support them."));
     162                error_with_progname = true;
     163              }
     164          }
     165  
     166        if (!output_syntax->supports_plurals)
     167          {
     168            const lex_pos_ty *has_plural;
     169            size_t k;
     170  
     171            has_plural = NULL;
     172            for (k = 0; k < mdlp->nitems; k++)
     173              {
     174                message_list_ty *mlp = mdlp->item[k]->messages;
     175                size_t j;
     176  
     177                for (j = 0; j < mlp->nitems; j++)
     178                  {
     179                    message_ty *mp = mlp->item[j];
     180  
     181                    if (mp->msgid_plural != NULL)
     182                      {
     183                        has_plural = &mp->pos;
     184                        break;
     185                      }
     186                  }
     187              }
     188  
     189            if (has_plural != NULL)
     190              {
     191                error_with_progname = false;
     192                if (output_syntax->alternative_is_java_class)
     193                  po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
     194                             has_plural->file_name, has_plural->line_number,
     195                             (size_t)(-1), false,
     196                             _("message catalog has plural form translations, but the output format does not support them. Try generating a Java class using \"msgfmt --java\", instead of a properties file."));
     197                else
     198                  po_xerror (PO_SEVERITY_FATAL_ERROR, NULL,
     199                             has_plural->file_name, has_plural->line_number,
     200                             (size_t)(-1), false,
     201                             _("message catalog has plural form translations, but the output format does not support them."));
     202                error_with_progname = true;
     203              }
     204          }
     205      }
     206  
     207    to_stdout = (filename == NULL || strcmp (filename, "-") == 0
     208                 || strcmp (filename, "/dev/stdout") == 0);
     209  
     210  #if ENABLE_COLOR
     211    if (output_syntax->supports_color
     212        && (color_mode == color_yes
     213            || (color_mode == color_tty && to_stdout
     214                && isatty (STDOUT_FILENO)
     215                && getenv ("NO_COLOR") == NULL)))
     216      {
     217        int fd;
     218        ostream_t stream;
     219  
     220        /* Open the output file.  */
     221        if (!to_stdout)
     222          {
     223            fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC,
     224                       /* 0666 in portable POSIX notation: */
     225                       S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
     226            if (fd < 0)
     227              {
     228                const char *errno_description = strerror (errno);
     229                po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
     230                           xasprintf ("%s: %s",
     231                                      xasprintf (_("cannot create output file \"%s\""),
     232                                                 filename),
     233                                      errno_description));
     234              }
     235          }
     236        else
     237          {
     238            fd = STDOUT_FILENO;
     239            filename = _("standard output");
     240          }
     241  
     242        style_file_prepare ("PO_STYLE",
     243                            "GETTEXTSTYLESDIR", relocate (GETTEXTSTYLESDIR),
     244                            "po-default.css");
     245        stream =
     246          styled_ostream_create (fd, filename, TTYCTL_AUTO, style_file_name);
     247        output_syntax->print (mdlp, stream, page_width, debug);
     248        ostream_free (stream);
     249  
     250        /* Make sure nothing went wrong.  */
     251        if (close (fd) < 0)
     252          {
     253            const char *errno_description = strerror (errno);
     254            po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
     255                       xasprintf ("%s: %s",
     256                                  xasprintf (_("error while writing \"%s\" file"),
     257                                             filename),
     258                                  errno_description));
     259          }
     260      }
     261    else
     262  #endif
     263      {
     264        FILE *fp;
     265        file_ostream_t stream;
     266  
     267        /* Open the output file.  */
     268        if (!to_stdout)
     269          {
     270            fp = fopen (filename, "wb");
     271            if (fp == NULL)
     272              {
     273                const char *errno_description = strerror (errno);
     274                po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
     275                           xasprintf ("%s: %s",
     276                                      xasprintf (_("cannot create output file \"%s\""),
     277                                                 filename),
     278                                      errno_description));
     279              }
     280          }
     281        else
     282          {
     283            fp = stdout;
     284            filename = _("standard output");
     285          }
     286  
     287        stream = file_ostream_create (fp);
     288  
     289  #if ENABLE_COLOR
     290        if (output_syntax->supports_color && color_mode == color_html)
     291          {
     292            html_styled_ostream_t html_stream;
     293  
     294            /* Convert mdlp to UTF-8 encoding.  */
     295            if (mdlp->encoding != po_charset_utf8)
     296              {
     297                mdlp = msgdomain_list_copy (mdlp, 0);
     298                mdlp = iconv_msgdomain_list (mdlp, po_charset_utf8, false, NULL);
     299              }
     300  
     301            style_file_prepare ("PO_STYLE",
     302                                "GETTEXTSTYLESDIR", relocate (GETTEXTSTYLESDIR),
     303                                "po-default.css");
     304            html_stream = html_styled_ostream_create (stream, style_file_name);
     305            output_syntax->print (mdlp, html_stream, page_width, debug);
     306            ostream_free (html_stream);
     307          }
     308        else
     309          {
     310            noop_styled_ostream_t styled_stream;
     311  
     312            styled_stream = noop_styled_ostream_create (stream, false);
     313            output_syntax->print (mdlp, styled_stream, page_width, debug);
     314            ostream_free (styled_stream);
     315          }
     316  #else
     317        output_syntax->print (mdlp, stream, page_width, debug);
     318        /* Don't call ostream_free if file_ostream_create is a dummy.  */
     319        if (stream != fp)
     320  #endif
     321          ostream_free (stream);
     322  
     323        /* Make sure nothing went wrong.  */
     324        if (fwriteerror (fp))
     325          {
     326            const char *errno_description = strerror (errno);
     327            po_xerror (PO_SEVERITY_FATAL_ERROR, NULL, NULL, 0, 0, false,
     328                       xasprintf ("%s: %s",
     329                                  xasprintf (_("error while writing \"%s\" file"),
     330                                             filename),
     331                                  errno_description));
     332          }
     333      }
     334  }
     335  
     336  
     337  /* =============================== Sorting. ================================ */
     338  
     339  
     340  static int
     341  cmp_by_msgid (const void *va, const void *vb)
     342  {
     343    const message_ty *a = *(const message_ty **) va;
     344    const message_ty *b = *(const message_ty **) vb;
     345  
     346    /* Because msgids normally contain only ASCII characters or are UTF-8
     347       encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And
     348       strcoll() in a C.UTF-8 locale is the same as strcmp().  */
     349    int cmp = strcmp (a->msgid, b->msgid);
     350    if (cmp != 0)
     351      return cmp;
     352  
     353    /* If the msgids are equal, disambiguate by comparing the contexts.  */
     354    if (a->msgctxt == b->msgctxt)
     355      return 0;
     356    if (a->msgctxt == NULL)
     357      return -1;
     358    if (b->msgctxt == NULL)
     359      return 1;
     360    return strcmp (a->msgctxt, b->msgctxt);
     361  }
     362  
     363  
     364  void
     365  msgdomain_list_sort_by_msgid (msgdomain_list_ty *mdlp)
     366  {
     367    size_t k;
     368  
     369    for (k = 0; k < mdlp->nitems; k++)
     370      {
     371        message_list_ty *mlp = mdlp->item[k]->messages;
     372  
     373        if (mlp->nitems > 0)
     374          qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_msgid);
     375      }
     376  }
     377  
     378  
     379  /* Sort the file positions of every message.  */
     380  
     381  static int
     382  cmp_filepos (const void *va, const void *vb)
     383  {
     384    const lex_pos_ty *a = (const lex_pos_ty *) va;
     385    const lex_pos_ty *b = (const lex_pos_ty *) vb;
     386    int cmp;
     387  
     388    cmp = strcmp (a->file_name, b->file_name);
     389    if (cmp == 0)
     390      cmp = (int) a->line_number - (int) b->line_number;
     391  
     392    return cmp;
     393  }
     394  
     395  static void
     396  msgdomain_list_sort_filepos (msgdomain_list_ty *mdlp)
     397  {
     398    size_t j, k;
     399  
     400    for (k = 0; k < mdlp->nitems; k++)
     401      {
     402        message_list_ty *mlp = mdlp->item[k]->messages;
     403  
     404        for (j = 0; j < mlp->nitems; j++)
     405          {
     406            message_ty *mp = mlp->item[j];
     407  
     408            if (mp->filepos_count > 0)
     409              qsort (mp->filepos, mp->filepos_count, sizeof (mp->filepos[0]),
     410                     cmp_filepos);
     411          }
     412      }
     413  }
     414  
     415  
     416  /* Sort the messages according to the file position.  */
     417  
     418  static int
     419  cmp_by_filepos (const void *va, const void *vb)
     420  {
     421    const message_ty *a = *(const message_ty **) va;
     422    const message_ty *b = *(const message_ty **) vb;
     423    int cmp;
     424  
     425    /* No filepos is smaller than any other filepos.  */
     426    cmp = (a->filepos_count != 0) - (b->filepos_count != 0);
     427    if (cmp != 0)
     428      return cmp;
     429  
     430    if (a->filepos_count != 0)
     431      {
     432        /* Compare on the file names...  */
     433        cmp = strcmp (a->filepos[0].file_name, b->filepos[0].file_name);
     434        if (cmp != 0)
     435          return cmp;
     436  
     437        /* If they are equal, compare on the line numbers...  */
     438        cmp = a->filepos[0].line_number - b->filepos[0].line_number;
     439        if (cmp != 0)
     440          return cmp;
     441      }
     442  
     443    /* If they are equal, compare on the msgid strings.  */
     444    /* Because msgids normally contain only ASCII characters or are UTF-8
     445       encoded, it is OK to sort them as if we were in a C.UTF-8 locale. And
     446       strcoll() in a C.UTF-8 locale is the same as strcmp().  */
     447    cmp = strcmp (a->msgid, b->msgid);
     448    if (cmp != 0)
     449      return cmp;
     450  
     451    /* If the msgids are equal, disambiguate by comparing the contexts.  */
     452    if (a->msgctxt == b->msgctxt)
     453      return 0;
     454    if (a->msgctxt == NULL)
     455      return -1;
     456    if (b->msgctxt == NULL)
     457      return 1;
     458    return strcmp (a->msgctxt, b->msgctxt);
     459  }
     460  
     461  
     462  void
     463  msgdomain_list_sort_by_filepos (msgdomain_list_ty *mdlp)
     464  {
     465    size_t k;
     466  
     467    /* It makes sense to compare filepos[0] of different messages only after
     468       the filepos[] array of each message has been sorted.  Sort it now.  */
     469    msgdomain_list_sort_filepos (mdlp);
     470  
     471    for (k = 0; k < mdlp->nitems; k++)
     472      {
     473        message_list_ty *mlp = mdlp->item[k]->messages;
     474  
     475        if (mlp->nitems > 0)
     476          qsort (mlp->item, mlp->nitems, sizeof (mlp->item[0]), cmp_by_filepos);
     477      }
     478  }