(root)/
gettext-0.22.4/
gettext-tools/
src/
format-perl-brace.c
       1  /* Perl brace format strings.
       2     Copyright (C) 2004, 2006-2007, 2009, 2019-2020 Free Software Foundation, Inc.
       3     Written by Bruno Haible <bruno@clisp.org>, 2003.
       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  #include <string.h>
      25  
      26  #include "format.h"
      27  #include "xalloc.h"
      28  #include "gettext.h"
      29  
      30  #define _(str) gettext (str)
      31  
      32  /* Perl brace format strings are supported by Guido Flohr's libintl-perl
      33     package, more precisely by the __expand and __x functions therein.
      34     A format string directive here consists of
      35       - an opening brace '{',
      36       - an identifier [_A-Za-z][_0-9A-Za-z]*,
      37       - a closing brace '}'.
      38   */
      39  
      40  struct named_arg
      41  {
      42    char *name;
      43  };
      44  
      45  struct spec
      46  {
      47    unsigned int directives;
      48    unsigned int named_arg_count;
      49    struct named_arg *named;
      50  };
      51  
      52  
      53  static int
      54  named_arg_compare (const void *p1, const void *p2)
      55  {
      56    return strcmp (((const struct named_arg *) p1)->name,
      57                   ((const struct named_arg *) p2)->name);
      58  }
      59  
      60  static void *
      61  format_parse (const char *format, bool translated, char *fdi,
      62                char **invalid_reason)
      63  {
      64    const char *const format_start = format;
      65    struct spec spec;
      66    unsigned int named_allocated;
      67    struct spec *result;
      68  
      69    spec.directives = 0;
      70    spec.named_arg_count = 0;
      71    spec.named = NULL;
      72    named_allocated = 0;
      73  
      74    for (; *format != '\0';)
      75      if (*format++ == '{')
      76        {
      77          const char *f = format;
      78          char c;
      79  
      80          c = *f;
      81          if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_')
      82            {
      83              do
      84                c = *++f;
      85              while ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_'
      86                     || (c >= '0' && c <= '9'));
      87              if (c == '}')
      88                {
      89                  /* A directive.  */
      90                  char *name;
      91                  const char *name_start = format;
      92                  const char *name_end = f;
      93                  size_t n = name_end - name_start;
      94  
      95                  FDI_SET (format - 1, FMTDIR_START);
      96  
      97                  name = XNMALLOC (n + 1, char);
      98                  memcpy (name, name_start, n);
      99                  name[n] = '\0';
     100  
     101                  spec.directives++;
     102  
     103                  if (named_allocated == spec.named_arg_count)
     104                    {
     105                      named_allocated = 2 * named_allocated + 1;
     106                      spec.named = (struct named_arg *) xrealloc (spec.named, named_allocated * sizeof (struct named_arg));
     107                    }
     108                  spec.named[spec.named_arg_count].name = name;
     109                  spec.named_arg_count++;
     110  
     111                  FDI_SET (f, FMTDIR_END);
     112  
     113                  format = ++f;
     114                }
     115            }
     116        }
     117  
     118    /* Sort the named argument array, and eliminate duplicates.  */
     119    if (spec.named_arg_count > 1)
     120      {
     121        unsigned int i, j;
     122  
     123        qsort (spec.named, spec.named_arg_count, sizeof (struct named_arg),
     124               named_arg_compare);
     125  
     126        /* Remove duplicates: Copy from i to j, keeping 0 <= j <= i.  */
     127        for (i = j = 0; i < spec.named_arg_count; i++)
     128          if (j > 0 && strcmp (spec.named[i].name, spec.named[j-1].name) == 0)
     129            free (spec.named[i].name);
     130          else
     131            {
     132              if (j < i)
     133                spec.named[j].name = spec.named[i].name;
     134              j++;
     135            }
     136        spec.named_arg_count = j;
     137      }
     138  
     139    result = XMALLOC (struct spec);
     140    *result = spec;
     141    return result;
     142  }
     143  
     144  static void
     145  format_free (void *descr)
     146  {
     147    struct spec *spec = (struct spec *) descr;
     148  
     149    if (spec->named != NULL)
     150      {
     151        unsigned int i;
     152        for (i = 0; i < spec->named_arg_count; i++)
     153          free (spec->named[i].name);
     154        free (spec->named);
     155      }
     156    free (spec);
     157  }
     158  
     159  static int
     160  format_get_number_of_directives (void *descr)
     161  {
     162    struct spec *spec = (struct spec *) descr;
     163  
     164    return spec->directives;
     165  }
     166  
     167  static bool
     168  format_check (void *msgid_descr, void *msgstr_descr, bool equality,
     169                formatstring_error_logger_t error_logger,
     170                const char *pretty_msgid, const char *pretty_msgstr)
     171  {
     172    struct spec *spec1 = (struct spec *) msgid_descr;
     173    struct spec *spec2 = (struct spec *) msgstr_descr;
     174    bool err = false;
     175  
     176    if (spec1->named_arg_count + spec2->named_arg_count > 0)
     177      {
     178        unsigned int i, j;
     179        unsigned int n1 = spec1->named_arg_count;
     180        unsigned int n2 = spec2->named_arg_count;
     181  
     182        /* Check the argument names in spec1 are contained in those of spec2.
     183           Additional arguments in spec2 are allowed; they expand to themselves
     184           (including the surrounding braces) at runtime.
     185           Both arrays are sorted.  We search for the differences.  */
     186        for (i = 0, j = 0; i < n1 || j < n2; )
     187          {
     188            int cmp = (i >= n1 ? 1 :
     189                       j >= n2 ? -1 :
     190                       strcmp (spec1->named[i].name, spec2->named[j].name));
     191  
     192            if (cmp > 0)
     193              j++;
     194            else if (cmp < 0)
     195              {
     196                if (equality)
     197                  {
     198                    if (error_logger)
     199                      error_logger (_("a format specification for argument '%s' doesn't exist in '%s'"),
     200                                    spec1->named[i].name, pretty_msgstr);
     201                    err = true;
     202                    break;
     203                  }
     204                else
     205                  i++;
     206              }
     207            else
     208              j++, i++;
     209          }
     210      }
     211  
     212    return err;
     213  }
     214  
     215  
     216  struct formatstring_parser formatstring_perl_brace =
     217  {
     218    format_parse,
     219    format_free,
     220    format_get_number_of_directives,
     221    NULL,
     222    format_check
     223  };
     224  
     225  
     226  #ifdef TEST
     227  
     228  /* Test program: Print the argument list specification returned by
     229     format_parse for strings read from standard input.  */
     230  
     231  #include <stdio.h>
     232  
     233  static void
     234  format_print (void *descr)
     235  {
     236    struct spec *spec = (struct spec *) descr;
     237    unsigned int i;
     238  
     239    if (spec == NULL)
     240      {
     241        printf ("INVALID");
     242        return;
     243      }
     244  
     245    printf ("{");
     246    for (i = 0; i < spec->named_arg_count; i++)
     247      {
     248        if (i > 0)
     249          printf (", ");
     250        printf ("'%s'", spec->named[i].name);
     251      }
     252    printf ("}");
     253  }
     254  
     255  int
     256  main ()
     257  {
     258    for (;;)
     259      {
     260        char *line = NULL;
     261        size_t line_size = 0;
     262        int line_len;
     263        char *invalid_reason;
     264        void *descr;
     265  
     266        line_len = getline (&line, &line_size, stdin);
     267        if (line_len < 0)
     268          break;
     269        if (line_len > 0 && line[line_len - 1] == '\n')
     270          line[--line_len] = '\0';
     271  
     272        invalid_reason = NULL;
     273        descr = format_parse (line, false, NULL, &invalid_reason);
     274  
     275        format_print (descr);
     276        printf ("\n");
     277        if (descr == NULL)
     278          printf ("%s\n", invalid_reason);
     279  
     280        free (invalid_reason);
     281        free (line);
     282      }
     283  
     284    return 0;
     285  }
     286  
     287  /*
     288   * For Emacs M-x compile
     289   * Local Variables:
     290   * 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-perl-brace.c ../gnulib-lib/libgettextlib.la"
     291   * End:
     292   */
     293  
     294  #endif /* TEST */