(root)/
gettext-0.22.4/
gettext-tools/
src/
format.c
       1  /* Format strings.
       2     Copyright (C) 2001-2010, 2012-2013, 2015, 2019-2020, 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  #ifdef HAVE_CONFIG_H
      19  # include <config.h>
      20  #endif
      21  
      22  /* Specification.  */
      23  #include "format.h"
      24  
      25  #include <stdbool.h>
      26  #include <stdio.h>
      27  #include <stdlib.h>
      28  
      29  #include "message.h"
      30  #include "gettext.h"
      31  
      32  #define _(str) gettext (str)
      33  
      34  /* Table of all format string parsers.  */
      35  struct formatstring_parser *formatstring_parsers[NFORMATS] =
      36  {
      37    /* format_c */                &formatstring_c,
      38    /* format_objc */             &formatstring_objc,
      39    /* format_cplusplus_brace */  &formatstring_cplusplus_brace,
      40    /* format_python */           &formatstring_python,
      41    /* format_python_brace */     &formatstring_python_brace,
      42    /* format_java */             &formatstring_java,
      43    /* format_java_printf */      &formatstring_java_printf,
      44    /* format_csharp */           &formatstring_csharp,
      45    /* format_javascript */       &formatstring_javascript,
      46    /* format_scheme */           &formatstring_scheme,
      47    /* format_lisp */             &formatstring_lisp,
      48    /* format_elisp */            &formatstring_elisp,
      49    /* format_librep */           &formatstring_librep,
      50    /* format_ruby */             &formatstring_ruby,
      51    /* format_sh */               &formatstring_sh,
      52    /* format_awk */              &formatstring_awk,
      53    /* format_lua */              &formatstring_lua,
      54    /* format_pascal */           &formatstring_pascal,
      55    /* format_smalltalk */        &formatstring_smalltalk,
      56    /* format_qt */               &formatstring_qt,
      57    /* format_qt_plural */        &formatstring_qt_plural,
      58    /* format_kde */              &formatstring_kde,
      59    /* format_kde_kuit */         &formatstring_kde_kuit,
      60    /* format_boost */            &formatstring_boost,
      61    /* format_tcl */              &formatstring_tcl,
      62    /* format_perl */             &formatstring_perl,
      63    /* format_perl_brace */       &formatstring_perl_brace,
      64    /* format_php */              &formatstring_php,
      65    /* format_gcc_internal */     &formatstring_gcc_internal,
      66    /* format_gfc_internal */     &formatstring_gfc_internal,
      67    /* format_ycp */              &formatstring_ycp
      68  };
      69  
      70  /* Check whether both formats strings contain compatible format
      71     specifications for format type i (0 <= i < NFORMATS).  */
      72  int
      73  check_msgid_msgstr_format_i (const char *msgid, const char *msgid_plural,
      74                               const char *msgstr, size_t msgstr_len,
      75                               size_t i,
      76                               struct argument_range range,
      77                               const struct plural_distribution *distribution,
      78                               formatstring_error_logger_t error_logger)
      79  {
      80    int seen_errors = 0;
      81  
      82    /* At runtime, we can assume the program passes arguments that fit well for
      83       msgid.  We must signal an error if msgstr wants more arguments that msgid
      84       accepts.
      85       If msgstr wants fewer arguments than msgid, it wouldn't lead to a crash
      86       at runtime, but we nevertheless give an error because
      87       1) this situation occurs typically after the programmer has added some
      88          arguments to msgid, so we must make the translator specially aware
      89          of it (more than just "fuzzy"),
      90       2) it is generally wrong if a translation wants to ignore arguments that
      91          are used by other translations.  */
      92  
      93    struct formatstring_parser *parser = formatstring_parsers[i];
      94    char *invalid_reason = NULL;
      95    void *msgid_descr =
      96      parser->parse (msgid_plural != NULL ? msgid_plural : msgid, false, NULL,
      97                     &invalid_reason);
      98  
      99    if (msgid_descr != NULL)
     100      {
     101        const char *pretty_msgid =
     102          (msgid_plural != NULL ? "msgid_plural" : "msgid");
     103        char buf[18+1];
     104        const char *pretty_msgstr = "msgstr";
     105        bool has_plural_translations = (strlen (msgstr) + 1 < msgstr_len);
     106        const char *p_end = msgstr + msgstr_len;
     107        const char *p;
     108        unsigned int j;
     109  
     110        for (p = msgstr, j = 0; p < p_end; p += strlen (p) + 1, j++)
     111          {
     112            void *msgstr_descr;
     113  
     114            if (msgid_plural != NULL)
     115              {
     116                sprintf (buf, "msgstr[%u]", j);
     117                pretty_msgstr = buf;
     118              }
     119  
     120            msgstr_descr = parser->parse (p, true, NULL, &invalid_reason);
     121  
     122            if (msgstr_descr != NULL)
     123              {
     124                /* Use strict checking (require same number of format
     125                   directives on both sides) if the message has no plurals,
     126                   or if msgid_plural exists but on the msgstr[] side
     127                   there is only msgstr[0], or if distribution->often[j]
     128                   indicates that the variant applies to infinitely many
     129                   values of N and the N range is not restricted in a way
     130                   that the variant applies to only one N.
     131                   Use relaxed checking when there are at least two
     132                   msgstr[] forms and the distribution does not give more
     133                   precise information.  */
     134                bool strict_checking =
     135                  (msgid_plural == NULL
     136                   || !has_plural_translations
     137                   || (distribution != NULL
     138                       && distribution->often != NULL
     139                       && j < distribution->often_length
     140                       && distribution->often[j]
     141                       && !(has_range_p (range)
     142                            && distribution->histogram (distribution,
     143                                                        range.min, range.max, j)
     144                               <= 1)));
     145  
     146                if (parser->check (msgid_descr, msgstr_descr,
     147                                   strict_checking,
     148                                   error_logger, pretty_msgid, pretty_msgstr))
     149                  seen_errors++;
     150  
     151                parser->free (msgstr_descr);
     152              }
     153            else
     154              {
     155                error_logger (_("'%s' is not a valid %s format string, unlike '%s'. Reason: %s"),
     156                              pretty_msgstr, format_language_pretty[i],
     157                              pretty_msgid, invalid_reason);
     158                seen_errors++;
     159                free (invalid_reason);
     160              }
     161          }
     162  
     163        parser->free (msgid_descr);
     164      }
     165    else
     166      free (invalid_reason);
     167  
     168    return seen_errors;
     169  }
     170  
     171  /* Check whether both formats strings contain compatible format
     172     specifications.
     173     Return the number of errors that were seen.  */
     174  int
     175  check_msgid_msgstr_format (const char *msgid, const char *msgid_plural,
     176                             const char *msgstr, size_t msgstr_len,
     177                             const enum is_format is_format[NFORMATS],
     178                             struct argument_range range,
     179                             const struct plural_distribution *distribution,
     180                             formatstring_error_logger_t error_logger)
     181  {
     182    int seen_errors = 0;
     183    size_t i;
     184  
     185    /* We check only those messages for which the msgid's is_format flag
     186       is one of 'yes' or 'possible'.  We don't check msgids with is_format
     187       'no' or 'impossible', to obey the programmer's order.  We don't check
     188       msgids with is_format 'undecided' because that would introduce too
     189       many checks, thus forcing the programmer to add "xgettext: no-c-format"
     190       anywhere where a translator wishes to use a percent sign.  */
     191    for (i = 0; i < NFORMATS; i++)
     192      if (possible_format_p (is_format[i]))
     193        seen_errors += check_msgid_msgstr_format_i (msgid, msgid_plural,
     194                                                    msgstr, msgstr_len, i,
     195                                                    range,
     196                                                    distribution,
     197                                                    error_logger);
     198  
     199    return seen_errors;
     200  }