(root)/
gettext-0.22.4/
gettext-tools/
src/
format-kde.c
       1  /* KDE format strings.
       2     Copyright (C) 2003-2004, 2006-2007, 2009, 2019-2020, 2023 Free Software Foundation, Inc.
       3     Written by Bruno Haible <bruno@clisp.org>, 2007.
       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  #include <stdbool.h>
      23  #include <stdlib.h>
      24  
      25  #include "format.h"
      26  #include "xalloc.h"
      27  #include "xvasprintf.h"
      28  #include "gettext.h"
      29  
      30  #define _(str) gettext (str)
      31  
      32  /* KDE 4 format strings are processed by method
      33     KLocalizedStringPrivate::substituteSimple(string,'%',false) in
      34     kde4libs-3.93.0.orig/kdecore/localization/klocalizedstring.cpp .
      35     A directive
      36       - starts with '%',
      37       - is followed by a non-zero digit and optionally more digits. All
      38         the following digits are eaten up.
      39     An unterminated directive ('%' not followed by a digit or at the end) is
      40     not an error.
      41     %1 denotes the first argument, %2 the second argument, etc.
      42     The set of used argument numbers must be of the form {1,...,n} or
      43     {1,...,n} \ {m}: one of the supplied arguments may be ignored by the
      44     format string. This allows the processing of singular forms (msgstr[0]).
      45     Which argument may be skipped, depends on the argument types at runtime;
      46     since xgettext cannot extract this info, it is considered unknown here.  */
      47  
      48  struct numbered_arg
      49  {
      50    unsigned int number;
      51  };
      52  
      53  struct spec
      54  {
      55    unsigned int directives;
      56    unsigned int numbered_arg_count;
      57    struct numbered_arg *numbered;
      58  };
      59  
      60  static int
      61  numbered_arg_compare (const void *p1, const void *p2)
      62  {
      63    /* Subtract 1, because argument number 0 can only occur through overflow.  */
      64    unsigned int n1 = ((const struct numbered_arg *) p1)->number - 1;
      65    unsigned int n2 = ((const struct numbered_arg *) p2)->number - 1;
      66  
      67    return (n1 > n2 ? 1 : n1 < n2 ? -1 : 0);
      68  }
      69  
      70  static void *
      71  format_parse (const char *format, bool translated, char *fdi,
      72                char **invalid_reason)
      73  {
      74    const char *const format_start = format;
      75    struct spec spec;
      76    unsigned int numbered_allocated;
      77    struct spec *result;
      78  
      79    spec.directives = 0;
      80    spec.numbered_arg_count = 0;
      81    spec.numbered = NULL;
      82    numbered_allocated = 0;
      83  
      84    for (; *format != '\0';)
      85      if (*format++ == '%')
      86        {
      87          const char *dir_start = format - 1;
      88  
      89          if (*format > '0' && *format <= '9')
      90            {
      91              /* A directive.  */
      92              unsigned int number;
      93  
      94              FDI_SET (dir_start, FMTDIR_START);
      95              spec.directives++;
      96  
      97              number = *format - '0';
      98              while (format[1] >= '0' && format[1] <= '9')
      99                {
     100                  number = 10 * number + (format[1] - '0');
     101                  format++;
     102                }
     103  
     104              if (numbered_allocated == spec.numbered_arg_count)
     105                {
     106                  numbered_allocated = 2 * numbered_allocated + 1;
     107                  spec.numbered = (struct numbered_arg *) xrealloc (spec.numbered, numbered_allocated * sizeof (struct numbered_arg));
     108                }
     109              spec.numbered[spec.numbered_arg_count].number = number;
     110              spec.numbered_arg_count++;
     111  
     112              FDI_SET (format, FMTDIR_END);
     113  
     114              format++;
     115            }
     116        }
     117  
     118    /* Sort the numbered argument array, and eliminate duplicates.  */
     119    if (spec.numbered_arg_count > 1)
     120      {
     121        unsigned int i, j;
     122  
     123        qsort (spec.numbered, spec.numbered_arg_count,
     124               sizeof (struct numbered_arg), numbered_arg_compare);
     125  
     126        /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
     127        for (i = j = 0; i < spec.numbered_arg_count; i++)
     128          if (j > 0 && spec.numbered[i].number == spec.numbered[j-1].number)
     129            ;
     130          else
     131            {
     132              if (j < i)
     133                spec.numbered[j].number = spec.numbered[i].number;
     134              j++;
     135            }
     136        spec.numbered_arg_count = j;
     137      }
     138    /* Now spec.numbered[i] >= i + 1 for i = 0,..,spec.numbered_arg_count-1
     139       (since the numbered argument counts are strictly increasing, considering
     140       0 as overflow).  */
     141  
     142    /* Verify that the argument numbers are of the form {1,...,n} or
     143       {1,...,n} \ {m}.  */
     144    if (spec.numbered_arg_count > 0)
     145      {
     146        unsigned int i;
     147  
     148        i = 0;
     149        for (; i < spec.numbered_arg_count; i++)
     150          if (spec.numbered[i].number > i + 1)
     151            {
     152              unsigned int first_gap = i + 1;
     153              for (; i < spec.numbered_arg_count; i++)
     154                if (spec.numbered[i].number > i + 2)
     155                  {
     156                    unsigned int second_gap = i + 2;
     157                    *invalid_reason =
     158                      xasprintf (_("The string refers to argument number %u but ignores the arguments %u and %u."),
     159                                 spec.numbered[i].number, first_gap, second_gap);
     160                    goto bad_format;
     161                  }
     162               break;
     163            }
     164      }
     165  
     166    result = XMALLOC (struct spec);
     167    *result = spec;
     168    return result;
     169  
     170   bad_format:
     171    if (spec.numbered != NULL)
     172      free (spec.numbered);
     173    return NULL;
     174  }
     175  
     176  static void
     177  format_free (void *descr)
     178  {
     179    struct spec *spec = (struct spec *) descr;
     180  
     181    if (spec->numbered != NULL)
     182      free (spec->numbered);
     183    free (spec);
     184  }
     185  
     186  static int
     187  format_get_number_of_directives (void *descr)
     188  {
     189    struct spec *spec = (struct spec *) descr;
     190  
     191    return spec->directives;
     192  }
     193  
     194  static bool
     195  format_check (void *msgid_descr, void *msgstr_descr, bool equality,
     196                formatstring_error_logger_t error_logger,
     197                const char *pretty_msgid, const char *pretty_msgstr)
     198  {
     199    struct spec *spec1 = (struct spec *) msgid_descr;
     200    struct spec *spec2 = (struct spec *) msgstr_descr;
     201    bool err = false;
     202  
     203    if (spec1->numbered_arg_count + spec2->numbered_arg_count > 0)
     204      {
     205        unsigned int i, j;
     206        unsigned int n1 = spec1->numbered_arg_count;
     207        unsigned int n2 = spec2->numbered_arg_count;
     208        unsigned int missing = 0; /* only used if !equality */
     209  
     210        /* Check that the argument numbers are the same.
     211           Both arrays are sorted.  We search for the first difference.  */
     212        for (i = 0, j = 0; i < n1 || j < n2; )
     213          {
     214            int cmp = (i >= n1 ? 1 :
     215                       j >= n2 ? -1 :
     216                       spec1->numbered[i].number > spec2->numbered[j].number ? 1 :
     217                       spec1->numbered[i].number < spec2->numbered[j].number ? -1 :
     218                       0);
     219  
     220            if (cmp > 0)
     221              {
     222                if (error_logger)
     223                  error_logger (_("a format specification for argument %u, as in '%s', doesn't exist in '%s'"),
     224                                spec2->numbered[j].number, pretty_msgstr,
     225                                pretty_msgid);
     226                err = true;
     227                break;
     228              }
     229            else if (cmp < 0)
     230              {
     231                if (equality)
     232                  {
     233                    if (error_logger)
     234                      error_logger (_("a format specification for argument %u doesn't exist in '%s'"),
     235                                    spec1->numbered[i].number, pretty_msgstr);
     236                    err = true;
     237                    break;
     238                  }
     239                else if (missing)
     240                  {
     241                    if (error_logger)
     242                      error_logger (_("a format specification for arguments %u and %u doesn't exist in '%s', only one argument may be ignored"),
     243                                    missing, spec1->numbered[i].number,
     244                                    pretty_msgstr);
     245                    err = true;
     246                    break;
     247                  }
     248                else
     249                  {
     250                    missing = spec1->numbered[i].number;
     251                    i++;
     252                  }
     253              }
     254            else
     255              j++, i++;
     256          }
     257      }
     258  
     259    return err;
     260  }
     261  
     262  
     263  struct formatstring_parser formatstring_kde =
     264  {
     265    format_parse,
     266    format_free,
     267    format_get_number_of_directives,
     268    NULL,
     269    format_check
     270  };
     271  
     272  
     273  #ifdef TEST
     274  
     275  /* Test program: Print the argument list specification returned by
     276     format_parse for strings read from standard input.  */
     277  
     278  #include <stdio.h>
     279  
     280  static void
     281  format_print (void *descr)
     282  {
     283    struct spec *spec = (struct spec *) descr;
     284    unsigned int last;
     285    unsigned int i;
     286  
     287    if (spec == NULL)
     288      {
     289        printf ("INVALID");
     290        return;
     291      }
     292  
     293    printf ("(");
     294    last = 1;
     295    for (i = 0; i < spec->numbered_arg_count; i++)
     296      {
     297        unsigned int number = spec->numbered[i].number;
     298  
     299        if (i > 0)
     300          printf (" ");
     301        if (number < last)
     302          abort ();
     303        for (; last < number; last++)
     304          printf ("_ ");
     305        last = number + 1;
     306      }
     307    printf (")");
     308  }
     309  
     310  int
     311  main ()
     312  {
     313    for (;;)
     314      {
     315        char *line = NULL;
     316        size_t line_size = 0;
     317        int line_len;
     318        char *invalid_reason;
     319        void *descr;
     320  
     321        line_len = getline (&line, &line_size, stdin);
     322        if (line_len < 0)
     323          break;
     324        if (line_len > 0 && line[line_len - 1] == '\n')
     325          line[--line_len] = '\0';
     326  
     327        invalid_reason = NULL;
     328        descr = format_parse (line, false, NULL, &invalid_reason);
     329  
     330        format_print (descr);
     331        printf ("\n");
     332        if (descr == NULL)
     333          printf ("%s\n", invalid_reason);
     334  
     335        free (invalid_reason);
     336        free (line);
     337      }
     338  
     339    return 0;
     340  }
     341  
     342  /*
     343   * For Emacs M-x compile
     344   * Local Variables:
     345   * compile-command: "/bin/sh ../libtool --tag=CC --mode=link gcc -o a.out -static -O -g -Wall -I.. -I../gnulib-lib -I../../gettext-runtime/intl -DHAVE_CONFIG_H -DTEST format-kde.c ../gnulib-lib/libgettextlib.la"
     346   * End:
     347   */
     348  
     349  #endif /* TEST */