1/* Perl brace format strings. 2 Copyright (C) 2004, 2006-2007 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 <http://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 40struct named_arg 41{ 42 char *name; 43}; 44 45struct spec 46{ 47 unsigned int directives; 48 unsigned int named_arg_count; 49 unsigned int allocated; 50 struct named_arg *named; 51}; 52 53 54static int 55named_arg_compare (const void *p1, const void *p2) 56{ 57 return strcmp (((const struct named_arg *) p1)->name, 58 ((const struct named_arg *) p2)->name); 59} 60 61static void * 62format_parse (const char *format, bool translated, char *fdi, 63 char **invalid_reason) 64{ 65 const char *const format_start = format; 66 struct spec spec; 67 struct spec *result; 68 69 spec.directives = 0; 70 spec.named_arg_count = 0; 71 spec.allocated = 0; 72 spec.named = NULL; 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 (spec.allocated == spec.named_arg_count) 104 { 105 spec.allocated = 2 * spec.allocated + 1; 106 spec.named = (struct named_arg *) xrealloc (spec.named, spec.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 144static void 145format_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 159static int 160format_get_number_of_directives (void *descr) 161{ 162 struct spec *spec = (struct spec *) descr; 163 164 return spec->directives; 165} 166 167static bool 168format_check (void *msgid_descr, void *msgstr_descr, bool equality, 169 formatstring_error_logger_t error_logger, 170 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 216struct 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 233static void 234format_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 255int 256main () 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../intl -DHAVE_CONFIG_H -DTEST format-perl-brace.c ../gnulib-lib/libgettextlib.la" 291 * End: 292 */ 293 294#endif /* TEST */ 295