1 /* xgettext glade backend. 2 Copyright (C) 2002-2003, 2005-2006 Free Software Foundation, Inc. 3 4 This file was written by Bruno Haible <haible (at) clisp.cons.org>, 2002. 5 6 This program is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; if not, write to the Free Software Foundation, 18 Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ 19 20 #ifdef HAVE_CONFIG_H 21 # include "config.h" 22 #endif 23 24 #include <errno.h> 25 #include <stdbool.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #if DYNLOAD_LIBEXPAT 30 # include <dlfcn.h> 31 #else 32 # if HAVE_LIBEXPAT 33 # include <expat.h> 34 # endif 35 #endif 36 37 #include "message.h" 38 #include "xgettext.h" 39 #include "x-glade.h" 40 #include "error.h" 41 #include "xerror.h" 42 #include "xvasprintf.h" 43 #include "basename.h" 44 #include "progname.h" 45 #include "xalloc.h" 46 #include "exit.h" 47 #include "hash.h" 48 #include "po-charset.h" 49 #include "gettext.h" 50 51 #define _(s) gettext(s) 52 53 54 /* glade is an XML based format. Some example files are contained in 55 libglade-0.16. */ 56 57 58 /* ====================== Keyword set customization. ====================== */ 59 60 /* If true extract all strings. */ 61 static bool extract_all = false; 62 63 static hash_table keywords; 64 static bool default_keywords = true; 65 66 67 void 68 x_glade_extract_all () 69 { 70 extract_all = true; 71 } 72 73 74 void 75 x_glade_keyword (const char *name) 76 { 77 if (name == NULL) 78 default_keywords = false; 79 else 80 { 81 if (keywords.table == NULL) 82 hash_init (&keywords, 100); 83 84 hash_insert_entry (&keywords, name, strlen (name), NULL); 85 } 86 } 87 88 /* Finish initializing the keywords hash table. 89 Called after argument processing, before each file is processed. */ 90 static void 91 init_keywords () 92 { 93 if (default_keywords) 94 { 95 /* When adding new keywords here, also update the documentation in 96 xgettext.texi! */ 97 x_glade_keyword ("label"); 98 x_glade_keyword ("title"); 99 x_glade_keyword ("text"); 100 x_glade_keyword ("format"); 101 x_glade_keyword ("copyright"); 102 x_glade_keyword ("comments"); 103 x_glade_keyword ("preview_text"); 104 x_glade_keyword ("tooltip"); 105 default_keywords = false; 106 } 107 } 108 109 110 /* ===================== Dynamic loading of libexpat. ===================== */ 111 112 #if DYNLOAD_LIBEXPAT 113 114 typedef void *XML_Parser; 115 typedef char XML_Char; 116 typedef char XML_LChar; 117 enum XML_Error { XML_ERROR_NONE }; 118 typedef void (*XML_StartElementHandler) (void *userData, const XML_Char *name, const XML_Char **atts); 119 typedef void (*XML_EndElementHandler) (void *userData, const XML_Char *name); 120 typedef void (*XML_CharacterDataHandler) (void *userData, const XML_Char *s, int len); 121 typedef void (*XML_CommentHandler) (void *userData, const XML_Char *data); 122 123 static XML_Parser (*p_XML_ParserCreate) (const XML_Char *encoding); 124 static void (*p_XML_SetElementHandler) (XML_Parser parser, XML_StartElementHandler start, XML_EndElementHandler end); 125 static void (*p_XML_SetCharacterDataHandler) (XML_Parser parser, XML_CharacterDataHandler handler); 126 static void (*p_XML_SetCommentHandler) (XML_Parser parser, XML_CommentHandler handler); 127 static int (*p_XML_Parse) (XML_Parser parser, const char *s, int len, int isFinal); 128 static enum XML_Error (*p_XML_GetErrorCode) (XML_Parser parser); 129 #if XML_MAJOR_VERSION >= 2 130 static XML_Size (*p_XML_GetCurrentLineNumber) (XML_Parser parser); 131 static XML_Size (*p_XML_GetCurrentColumnNumber) (XML_Parser parser); 132 #else 133 static int (*p_XML_GetCurrentLineNumber) (XML_Parser parser); 134 static int (*p_XML_GetCurrentColumnNumber) (XML_Parser parser); 135 #endif 136 static void (*p_XML_ParserFree) (XML_Parser parser); 137 static const XML_LChar * (*p_XML_ErrorString) (int code); 138 139 #define XML_ParserCreate (*p_XML_ParserCreate) 140 #define XML_SetElementHandler (*p_XML_SetElementHandler) 141 #define XML_SetCharacterDataHandler (*p_XML_SetCharacterDataHandler) 142 #define XML_SetCommentHandler (*p_XML_SetCommentHandler) 143 #define XML_Parse (*p_XML_Parse) 144 #define XML_GetErrorCode (*p_XML_GetErrorCode) 145 #define XML_GetCurrentLineNumber (*p_XML_GetCurrentLineNumber) 146 #define XML_GetCurrentColumnNumber (*p_XML_GetCurrentColumnNumber) 147 #define XML_ParserFree (*p_XML_ParserFree) 148 #define XML_ErrorString (*p_XML_ErrorString) 149 150 static int libexpat_loaded = 0; 151 152 static bool 153 load_libexpat () 154 { 155 if (libexpat_loaded == 0) 156 { 157 void *handle; 158 /* Be careful to use exactly the version of libexpat that matches the 159 binary interface declared in <expat.h>. */ 160 #if XML_MAJOR_VERSION >= 2 161 handle = dlopen ("libexpat.so.1", RTLD_LAZY); 162 #else 163 handle = dlopen ("libexpat.so.0", RTLD_LAZY); 164 #endif 165 if (handle != NULL 166 && (p_XML_ParserCreate = dlsym (handle, "XML_ParserCreate")) != NULL 167 && (p_XML_SetElementHandler = dlsym (handle, "XML_SetElementHandler")) != NULL 168 && (p_XML_SetCharacterDataHandler = dlsym (handle, "XML_SetCharacterDataHandler")) != NULL 169 && (p_XML_SetCommentHandler = dlsym (handle, "XML_SetCommentHandler")) != NULL 170 && (p_XML_Parse = dlsym (handle, "XML_Parse")) != NULL 171 && (p_XML_GetErrorCode = dlsym (handle, "XML_GetErrorCode")) != NULL 172 && (p_XML_GetCurrentLineNumber = dlsym (handle, "XML_GetCurrentLineNumber")) != NULL 173 && (p_XML_GetCurrentColumnNumber = dlsym (handle, "XML_GetCurrentColumnNumber")) != NULL 174 && (p_XML_ParserFree = dlsym (handle, "XML_ParserFree")) != NULL 175 && (p_XML_ErrorString = dlsym (handle, "XML_ErrorString")) != NULL) 176 libexpat_loaded = 1; 177 else 178 libexpat_loaded = -1; 179 } 180 return libexpat_loaded >= 0; 181 } 182 183 #define LIBEXPAT_AVAILABLE() (load_libexpat ()) 184 185 #elif HAVE_LIBEXPAT 186 187 #define LIBEXPAT_AVAILABLE() true 188 189 #endif 190 191 /* ============================= XML parsing. ============================= */ 192 193 #if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT 194 195 /* Accumulator for the extracted messages. */ 196 static message_list_ty *mlp; 197 198 /* Logical filename, used to label the extracted messages. */ 199 static char *logical_file_name; 200 201 /* XML parser. */ 202 static XML_Parser parser; 203 204 struct element_state 205 { 206 bool extract_string; 207 int lineno; 208 char *buffer; 209 size_t bufmax; 210 size_t buflen; 211 }; 212 static struct element_state *stack; 213 static size_t stack_size; 214 215 /* Ensures stack_size >= size. */ 216 static void 217 ensure_stack_size (size_t size) 218 { 219 if (size > stack_size) 220 { 221 stack_size = 2 * stack_size; 222 if (stack_size < size) 223 stack_size = size; 224 stack = 225 (struct element_state *) 226 xrealloc (stack, stack_size * sizeof (struct element_state)); 227 } 228 } 229 230 static size_t stack_depth; 231 232 /* Callback called when <element> is seen. */ 233 static void 234 start_element_handler (void *userData, const char *name, 235 const char **attributes) 236 { 237 struct element_state *p; 238 void *hash_result; 239 240 /* Increase stack depth. */ 241 stack_depth++; 242 ensure_stack_size (stack_depth + 1); 243 244 /* Don't extract a string for the containing element. */ 245 stack[stack_depth - 1].extract_string = false; 246 247 p = &stack[stack_depth]; 248 p->extract_string = extract_all; 249 /* In Glade 1, a few specific elements are translatable. */ 250 if (!p->extract_string) 251 p->extract_string = 252 (hash_find_entry (&keywords, name, strlen (name), &hash_result) == 0); 253 /* In Glade 2, all <property> and <atkproperty> elements are translatable 254 that have the attribute translatable="yes". */ 255 if (!p->extract_string 256 && (strcmp (name, "property") == 0 || strcmp (name, "atkproperty") == 0)) 257 { 258 bool has_translatable = false; 259 const char **attp = attributes; 260 while (*attp != NULL) 261 { 262 if (strcmp (attp[0], "translatable") == 0) 263 { 264 has_translatable = (strcmp (attp[1], "yes") == 0); 265 break; 266 } 267 attp += 2; 268 } 269 p->extract_string = has_translatable; 270 } 271 if (!p->extract_string 272 && strcmp (name, "atkaction") == 0) 273 { 274 const char **attp = attributes; 275 while (*attp != NULL) 276 { 277 if (strcmp (attp[0], "description") == 0) 278 { 279 if (strcmp (attp[1], "") != 0) 280 { 281 lex_pos_ty pos; 282 283 pos.file_name = logical_file_name; 284 pos.line_number = XML_GetCurrentLineNumber (parser); 285 286 remember_a_message (mlp, NULL, xstrdup (attp[1]), 287 null_context, &pos, savable_comment); 288 } 289 break; 290 } 291 attp += 2; 292 } 293 } 294 p->lineno = XML_GetCurrentLineNumber (parser); 295 p->buffer = NULL; 296 p->bufmax = 0; 297 p->buflen = 0; 298 if (!p->extract_string) 299 savable_comment_reset (); 300 } 301 302 /* Callback called when </element> is seen. */ 303 static void 304 end_element_handler (void *userData, const char *name) 305 { 306 struct element_state *p = &stack[stack_depth]; 307 308 /* Actually extract string. */ 309 if (p->extract_string) 310 { 311 /* Don't extract the empty string. */ 312 if (p->buflen > 0) 313 { 314 lex_pos_ty pos; 315 316 if (p->buflen == p->bufmax) 317 p->buffer = (char *) xrealloc (p->buffer, p->buflen + 1); 318 p->buffer[p->buflen] = '\0'; 319 320 pos.file_name = logical_file_name; 321 pos.line_number = p->lineno; 322 323 remember_a_message (mlp, NULL, p->buffer, null_context, &pos, 324 savable_comment); 325 p->buffer = NULL; 326 } 327 } 328 329 /* Free memory for this stack level. */ 330 if (p->buffer != NULL) 331 free (p->buffer); 332 333 /* Decrease stack depth. */ 334 stack_depth--; 335 336 savable_comment_reset (); 337 } 338 339 /* Callback called when some text is seen. */ 340 static void 341 character_data_handler (void *userData, const char *s, int len) 342 { 343 struct element_state *p = &stack[stack_depth]; 344 345 /* Accumulate character data. */ 346 if (len > 0) 347 { 348 if (p->buflen + len > p->bufmax) 349 { 350 p->bufmax = 2 * p->bufmax; 351 if (p->bufmax < p->buflen + len) 352 p->bufmax = p->buflen + len; 353 p->buffer = (char *) xrealloc (p->buffer, p->bufmax); 354 } 355 memcpy (p->buffer + p->buflen, s, len); 356 p->buflen += len; 357 } 358 } 359 360 /* Callback called when some comment text is seen. */ 361 static void 362 comment_handler (void *userData, const char *data) 363 { 364 /* Split multiline comment into lines, and remove leading and trailing 365 whitespace. */ 366 char *copy = xstrdup (data); 367 char *p = copy; 368 char *q; 369 370 for (p = copy; (q = strchr (p, '\n')) != NULL; p = q + 1) 371 { 372 while (p[0] == ' ' || p[0] == '\t') 373 p++; 374 while (q > p && (q[-1] == ' ' || q[-1] == '\t')) 375 q--; 376 *q = '\0'; 377 savable_comment_add (p); 378 } 379 q = p + strlen (p); 380 while (p[0] == ' ' || p[0] == '\t') 381 p++; 382 while (q > p && (q[-1] == ' ' || q[-1] == '\t')) 383 q--; 384 *q = '\0'; 385 savable_comment_add (p); 386 free (copy); 387 } 388 389 390 static void 391 do_extract_glade (FILE *fp, 392 const char *real_filename, const char *logical_filename, 393 msgdomain_list_ty *mdlp) 394 { 395 mlp = mdlp->item[0]->messages; 396 397 /* expat feeds us strings in UTF-8 encoding. */ 398 xgettext_current_source_encoding = po_charset_utf8; 399 400 logical_file_name = xstrdup (logical_filename); 401 402 init_keywords (); 403 404 parser = XML_ParserCreate (NULL); 405 if (parser == NULL) 406 error (EXIT_FAILURE, 0, _("memory exhausted")); 407 408 XML_SetElementHandler (parser, start_element_handler, end_element_handler); 409 XML_SetCharacterDataHandler (parser, character_data_handler); 410 XML_SetCommentHandler (parser, comment_handler); 411 412 stack_depth = 0; 413 414 while (!feof (fp)) 415 { 416 char buf[4096]; 417 int count = fread (buf, 1, sizeof buf, fp); 418 419 if (count == 0) 420 { 421 if (ferror (fp)) 422 error (EXIT_FAILURE, errno, _("\ 423 error while reading \"%s\""), real_filename); 424 /* EOF reached. */ 425 break; 426 } 427 428 if (XML_Parse (parser, buf, count, 0) == 0) 429 error (EXIT_FAILURE, 0, _("%s:%lu:%lu: %s"), logical_filename, 430 (unsigned long) XML_GetCurrentLineNumber (parser), 431 (unsigned long) XML_GetCurrentColumnNumber (parser) + 1, 432 XML_ErrorString (XML_GetErrorCode (parser))); 433 } 434 435 if (XML_Parse (parser, NULL, 0, 1) == 0) 436 error (EXIT_FAILURE, 0, _("%s:%lu:%lu: %s"), logical_filename, 437 (unsigned long) XML_GetCurrentLineNumber (parser), 438 (unsigned long) XML_GetCurrentColumnNumber (parser) + 1, 439 XML_ErrorString (XML_GetErrorCode (parser))); 440 441 XML_ParserFree (parser); 442 443 /* Close scanner. */ 444 logical_file_name = NULL; 445 parser = NULL; 446 } 447 448 #endif 449 450 void 451 extract_glade (FILE *fp, 452 const char *real_filename, const char *logical_filename, 453 flag_context_list_table_ty *flag_table, 454 msgdomain_list_ty *mdlp) 455 { 456 #if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT 457 if (LIBEXPAT_AVAILABLE ()) 458 do_extract_glade (fp, real_filename, logical_filename, mdlp); 459 else 460 #endif 461 { 462 multiline_error (xstrdup (""), 463 xasprintf (_("\ 464 Language \"glade\" is not supported. %s relies on expat.\n\ 465 This version was built without expat.\n"), 466 basename (program_name))); 467 exit (EXIT_FAILURE); 468 } 469 } 470