1/* xgettext glade backend. 2 Copyright (C) 2002-2003, 2005-2007 Free Software Foundation, Inc. 3 4 This file was written by Bruno Haible <haible@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 3 of the License, or 9 (at your option) 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, see <http://www.gnu.org/licenses/>. */ 18 19#ifdef HAVE_CONFIG_H 20# include "config.h" 21#endif 22 23/* Specification. */ 24#include "x-glade.h" 25 26#include <errno.h> 27#include <stdbool.h> 28#include <stdint.h> 29#include <stdio.h> 30#include <stdlib.h> 31#include <string.h> 32#if DYNLOAD_LIBEXPAT 33# include <dlfcn.h> 34#else 35# if HAVE_LIBEXPAT 36# include <expat.h> 37# endif 38#endif 39 40#include "message.h" 41#include "xgettext.h" 42#include "x-glade.h" 43#include "error.h" 44#include "xerror.h" 45#include "xvasprintf.h" 46#include "basename.h" 47#include "progname.h" 48#include "xalloc.h" 49#include "hash.h" 50#include "po-charset.h" 51#include "gettext.h" 52 53#define _(s) gettext(s) 54 55 56/* glade is an XML based format. Some example files are contained in 57 libglade-0.16. */ 58 59 60/* ====================== Keyword set customization. ====================== */ 61 62/* If true extract all strings. */ 63static bool extract_all = false; 64 65static hash_table keywords; 66static bool default_keywords = true; 67 68 69void 70x_glade_extract_all () 71{ 72 extract_all = true; 73} 74 75 76void 77x_glade_keyword (const char *name) 78{ 79 if (name == NULL) 80 default_keywords = false; 81 else 82 { 83 if (keywords.table == NULL) 84 hash_init (&keywords, 100); 85 86 hash_insert_entry (&keywords, name, strlen (name), NULL); 87 } 88} 89 90/* Finish initializing the keywords hash table. 91 Called after argument processing, before each file is processed. */ 92static void 93init_keywords () 94{ 95 if (default_keywords) 96 { 97 /* When adding new keywords here, also update the documentation in 98 xgettext.texi! */ 99 x_glade_keyword ("label"); 100 x_glade_keyword ("title"); 101 x_glade_keyword ("text"); 102 x_glade_keyword ("format"); 103 x_glade_keyword ("copyright"); 104 x_glade_keyword ("comments"); 105 x_glade_keyword ("preview_text"); 106 x_glade_keyword ("tooltip"); 107 default_keywords = false; 108 } 109} 110 111 112/* ======================= Different libexpat ABIs. ======================= */ 113 114/* There are three different ABIs of libexpat, regarding the functions 115 XML_GetCurrentLineNumber and XML_GetCurrentColumnNumber. 116 In expat < 2.0, they return an 'int'. 117 In expat >= 2.0, they return 118 - a 'long' if expat was compiled with the default flags, or 119 - a 'long long' if expat was compiled with -DXML_LARGE_SIZE. 120 But the <expat.h> include file does not contain the information whether 121 expat was compiled with -DXML_LARGE_SIZE; so the include file is lying! 122 For this information, we need to call XML_GetFeatureList(), for 123 expat >= 2.0.1; for expat = 2.0.0, we have to assume the default flags. */ 124 125#if !DYNLOAD_LIBEXPAT 126 127# if XML_MAJOR_VERSION >= 2 128 129/* expat >= 2.0 -> Return type is 'int64_t' worst-case. */ 130 131/* Put the function pointers into variables, because some GCC 4 versions 132 generate an abort when we convert symbol address to different function 133 pointer types. */ 134static void *p_XML_GetCurrentLineNumber = (void *) &XML_GetCurrentLineNumber; 135static void *p_XML_GetCurrentColumnNumber = (void *) &XML_GetCurrentColumnNumber; 136 137/* Return true if libexpat was compiled with -DXML_LARGE_SIZE. */ 138static bool 139is_XML_LARGE_SIZE_ABI (void) 140{ 141 static bool tested; 142 static bool is_large; 143 144 if (!tested) 145 { 146 const XML_Feature *features; 147 148 is_large = false; 149 for (features = XML_GetFeatureList (); features->name != NULL; features++) 150 if (strcmp (features->name, "XML_LARGE_SIZE") == 0) 151 { 152 is_large = true; 153 break; 154 } 155 156 tested = true; 157 } 158 return is_large; 159} 160 161static int64_t 162GetCurrentLineNumber (XML_Parser parser) 163{ 164 if (is_XML_LARGE_SIZE_ABI ()) 165 return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser); 166 else 167 return ((long (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser); 168} 169# define XML_GetCurrentLineNumber GetCurrentLineNumber 170 171static int64_t 172GetCurrentColumnNumber (XML_Parser parser) 173{ 174 if (is_XML_LARGE_SIZE_ABI ()) 175 return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser); 176 else 177 return ((long (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser); 178} 179# define XML_GetCurrentColumnNumber GetCurrentColumnNumber 180 181# else 182 183/* expat < 2.0 -> Return type is 'int'. */ 184 185# endif 186 187#endif 188 189 190/* ===================== Dynamic loading of libexpat. ===================== */ 191 192#if DYNLOAD_LIBEXPAT 193 194typedef struct 195 { 196 int major; 197 int minor; 198 int micro; 199 } 200 XML_Expat_Version; 201enum XML_FeatureEnum { XML_FEATURE_END = 0 }; 202typedef struct 203 { 204 enum XML_FeatureEnum feature; 205 const char *name; 206 long int value; 207 } 208 XML_Feature; 209typedef void *XML_Parser; 210typedef char XML_Char; 211typedef char XML_LChar; 212enum XML_Error { XML_ERROR_NONE }; 213typedef void (*XML_StartElementHandler) (void *userData, const XML_Char *name, const XML_Char **atts); 214typedef void (*XML_EndElementHandler) (void *userData, const XML_Char *name); 215typedef void (*XML_CharacterDataHandler) (void *userData, const XML_Char *s, int len); 216typedef void (*XML_CommentHandler) (void *userData, const XML_Char *data); 217 218static XML_Expat_Version (*p_XML_ExpatVersionInfo) (void); 219static const XML_Feature * (*p_XML_GetFeatureList) (void); 220static XML_Parser (*p_XML_ParserCreate) (const XML_Char *encoding); 221static void (*p_XML_SetElementHandler) (XML_Parser parser, XML_StartElementHandler start, XML_EndElementHandler end); 222static void (*p_XML_SetCharacterDataHandler) (XML_Parser parser, XML_CharacterDataHandler handler); 223static void (*p_XML_SetCommentHandler) (XML_Parser parser, XML_CommentHandler handler); 224static int (*p_XML_Parse) (XML_Parser parser, const char *s, int len, int isFinal); 225static enum XML_Error (*p_XML_GetErrorCode) (XML_Parser parser); 226static void *p_XML_GetCurrentLineNumber; 227static void *p_XML_GetCurrentColumnNumber; 228static void (*p_XML_ParserFree) (XML_Parser parser); 229static const XML_LChar * (*p_XML_ErrorString) (int code); 230 231#define XML_ExpatVersionInfo (*p_XML_ExpatVersionInfo) 232#define XML_GetFeatureList (*p_XML_GetFeatureList) 233 234enum XML_Size_ABI { is_int, is_long, is_int64_t }; 235 236static enum XML_Size_ABI 237get_XML_Size_ABI (void) 238{ 239 static bool tested; 240 static enum XML_Size_ABI abi; 241 242 if (!tested) 243 { 244 if (XML_ExpatVersionInfo () .major >= 2) 245 /* expat >= 2.0 -> XML_Size is 'int64_t' or 'long'. */ 246 { 247 const XML_Feature *features; 248 249 abi = is_long; 250 for (features = XML_GetFeatureList (); 251 features->name != NULL; 252 features++) 253 if (strcmp (features->name, "XML_LARGE_SIZE") == 0) 254 { 255 abi = is_int64_t; 256 break; 257 } 258 } 259 else 260 /* expat < 2.0 -> XML_Size is 'int'. */ 261 abi = is_int; 262 tested = true; 263 } 264 return abi; 265} 266 267#define XML_ParserCreate (*p_XML_ParserCreate) 268#define XML_SetElementHandler (*p_XML_SetElementHandler) 269#define XML_SetCharacterDataHandler (*p_XML_SetCharacterDataHandler) 270#define XML_SetCommentHandler (*p_XML_SetCommentHandler) 271#define XML_Parse (*p_XML_Parse) 272#define XML_GetErrorCode (*p_XML_GetErrorCode) 273 274static int64_t 275XML_GetCurrentLineNumber (XML_Parser parser) 276{ 277 switch (get_XML_Size_ABI ()) 278 { 279 case is_int: 280 return ((int (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser); 281 case is_long: 282 return ((long (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser); 283 case is_int64_t: 284 return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser); 285 default: 286 abort (); 287 } 288} 289 290static int64_t 291XML_GetCurrentColumnNumber (XML_Parser parser) 292{ 293 switch (get_XML_Size_ABI ()) 294 { 295 case is_int: 296 return ((int (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser); 297 case is_long: 298 return ((long (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser); 299 case is_int64_t: 300 return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser); 301 default: 302 abort (); 303 } 304} 305 306#define XML_ParserFree (*p_XML_ParserFree) 307#define XML_ErrorString (*p_XML_ErrorString) 308 309static int libexpat_loaded = 0; 310 311static bool 312load_libexpat () 313{ 314 if (libexpat_loaded == 0) 315 { 316 void *handle; 317 318 /* Try to load libexpat-2.x. */ 319 handle = dlopen ("libexpat.so.1", RTLD_LAZY); 320 if (handle == NULL) 321 /* Try to load libexpat-1.x. */ 322 handle = dlopen ("libexpat.so.0", RTLD_LAZY); 323 if (handle != NULL 324 && (p_XML_ExpatVersionInfo = 325 (XML_Expat_Version (*) (void)) 326 dlsym (handle, "XML_ExpatVersionInfo")) != NULL 327 && (p_XML_GetFeatureList = 328 (const XML_Feature * (*) (void)) 329 dlsym (handle, "XML_GetFeatureList")) != NULL 330 && (p_XML_ParserCreate = 331 (XML_Parser (*) (const XML_Char *)) 332 dlsym (handle, "XML_ParserCreate")) != NULL 333 && (p_XML_SetElementHandler = 334 (void (*) (XML_Parser, XML_StartElementHandler, XML_EndElementHandler)) 335 dlsym (handle, "XML_SetElementHandler")) != NULL 336 && (p_XML_SetCharacterDataHandler = 337 (void (*) (XML_Parser, XML_CharacterDataHandler)) 338 dlsym (handle, "XML_SetCharacterDataHandler")) != NULL 339 && (p_XML_SetCommentHandler = 340 (void (*) (XML_Parser, XML_CommentHandler)) 341 dlsym (handle, "XML_SetCommentHandler")) != NULL 342 && (p_XML_Parse = 343 (int (*) (XML_Parser, const char *, int, int)) 344 dlsym (handle, "XML_Parse")) != NULL 345 && (p_XML_GetErrorCode = 346 (enum XML_Error (*) (XML_Parser)) 347 dlsym (handle, "XML_GetErrorCode")) != NULL 348 && (p_XML_GetCurrentLineNumber = 349 dlsym (handle, "XML_GetCurrentLineNumber")) != NULL 350 && (p_XML_GetCurrentColumnNumber = 351 dlsym (handle, "XML_GetCurrentColumnNumber")) != NULL 352 && (p_XML_ParserFree = 353 (void (*) (XML_Parser)) 354 dlsym (handle, "XML_ParserFree")) != NULL 355 && (p_XML_ErrorString = 356 (const XML_LChar * (*) (int)) 357 dlsym (handle, "XML_ErrorString")) != NULL) 358 libexpat_loaded = 1; 359 else 360 libexpat_loaded = -1; 361 } 362 return libexpat_loaded >= 0; 363} 364 365#define LIBEXPAT_AVAILABLE() (load_libexpat ()) 366 367#elif HAVE_LIBEXPAT 368 369#define LIBEXPAT_AVAILABLE() true 370 371#endif 372 373/* ============================= XML parsing. ============================= */ 374 375#if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT 376 377/* Accumulator for the extracted messages. */ 378static message_list_ty *mlp; 379 380/* Logical filename, used to label the extracted messages. */ 381static char *logical_file_name; 382 383/* XML parser. */ 384static XML_Parser parser; 385 386struct element_state 387{ 388 bool extract_string; 389 int lineno; 390 char *buffer; 391 size_t bufmax; 392 size_t buflen; 393}; 394static struct element_state *stack; 395static size_t stack_size; 396 397/* Ensures stack_size >= size. */ 398static void 399ensure_stack_size (size_t size) 400{ 401 if (size > stack_size) 402 { 403 stack_size = 2 * stack_size; 404 if (stack_size < size) 405 stack_size = size; 406 stack = 407 (struct element_state *) 408 xrealloc (stack, stack_size * sizeof (struct element_state)); 409 } 410} 411 412static size_t stack_depth; 413 414/* Callback called when <element> is seen. */ 415static void 416start_element_handler (void *userData, const char *name, 417 const char **attributes) 418{ 419 struct element_state *p; 420 void *hash_result; 421 422 /* Increase stack depth. */ 423 stack_depth++; 424 ensure_stack_size (stack_depth + 1); 425 426 /* Don't extract a string for the containing element. */ 427 stack[stack_depth - 1].extract_string = false; 428 429 p = &stack[stack_depth]; 430 p->extract_string = extract_all; 431 /* In Glade 1, a few specific elements are translatable. */ 432 if (!p->extract_string) 433 p->extract_string = 434 (hash_find_entry (&keywords, name, strlen (name), &hash_result) == 0); 435 /* In Glade 2, all <property> and <atkproperty> elements are translatable 436 that have the attribute translatable="yes". */ 437 if (!p->extract_string 438 && (strcmp (name, "property") == 0 || strcmp (name, "atkproperty") == 0)) 439 { 440 bool has_translatable = false; 441 const char **attp = attributes; 442 while (*attp != NULL) 443 { 444 if (strcmp (attp[0], "translatable") == 0) 445 { 446 has_translatable = (strcmp (attp[1], "yes") == 0); 447 break; 448 } 449 attp += 2; 450 } 451 p->extract_string = has_translatable; 452 } 453 if (!p->extract_string 454 && strcmp (name, "atkaction") == 0) 455 { 456 const char **attp = attributes; 457 while (*attp != NULL) 458 { 459 if (strcmp (attp[0], "description") == 0) 460 { 461 if (strcmp (attp[1], "") != 0) 462 { 463 lex_pos_ty pos; 464 465 pos.file_name = logical_file_name; 466 pos.line_number = XML_GetCurrentLineNumber (parser); 467 468 remember_a_message (mlp, NULL, xstrdup (attp[1]), 469 null_context, &pos, savable_comment); 470 } 471 break; 472 } 473 attp += 2; 474 } 475 } 476 p->lineno = XML_GetCurrentLineNumber (parser); 477 p->buffer = NULL; 478 p->bufmax = 0; 479 p->buflen = 0; 480 if (!p->extract_string) 481 savable_comment_reset (); 482} 483 484/* Callback called when </element> is seen. */ 485static void 486end_element_handler (void *userData, const char *name) 487{ 488 struct element_state *p = &stack[stack_depth]; 489 490 /* Actually extract string. */ 491 if (p->extract_string) 492 { 493 /* Don't extract the empty string. */ 494 if (p->buflen > 0) 495 { 496 lex_pos_ty pos; 497 498 if (p->buflen == p->bufmax) 499 p->buffer = (char *) xrealloc (p->buffer, p->buflen + 1); 500 p->buffer[p->buflen] = '\0'; 501 502 pos.file_name = logical_file_name; 503 pos.line_number = p->lineno; 504 505 remember_a_message (mlp, NULL, p->buffer, null_context, &pos, 506 savable_comment); 507 p->buffer = NULL; 508 } 509 } 510 511 /* Free memory for this stack level. */ 512 if (p->buffer != NULL) 513 free (p->buffer); 514 515 /* Decrease stack depth. */ 516 stack_depth--; 517 518 savable_comment_reset (); 519} 520 521/* Callback called when some text is seen. */ 522static void 523character_data_handler (void *userData, const char *s, int len) 524{ 525 struct element_state *p = &stack[stack_depth]; 526 527 /* Accumulate character data. */ 528 if (len > 0) 529 { 530 if (p->buflen + len > p->bufmax) 531 { 532 p->bufmax = 2 * p->bufmax; 533 if (p->bufmax < p->buflen + len) 534 p->bufmax = p->buflen + len; 535 p->buffer = (char *) xrealloc (p->buffer, p->bufmax); 536 } 537 memcpy (p->buffer + p->buflen, s, len); 538 p->buflen += len; 539 } 540} 541 542/* Callback called when some comment text is seen. */ 543static void 544comment_handler (void *userData, const char *data) 545{ 546 /* Split multiline comment into lines, and remove leading and trailing 547 whitespace. */ 548 char *copy = xstrdup (data); 549 char *p = copy; 550 char *q; 551 552 for (p = copy; (q = strchr (p, '\n')) != NULL; p = q + 1) 553 { 554 while (p[0] == ' ' || p[0] == '\t') 555 p++; 556 while (q > p && (q[-1] == ' ' || q[-1] == '\t')) 557 q--; 558 *q = '\0'; 559 savable_comment_add (p); 560 } 561 q = p + strlen (p); 562 while (p[0] == ' ' || p[0] == '\t') 563 p++; 564 while (q > p && (q[-1] == ' ' || q[-1] == '\t')) 565 q--; 566 *q = '\0'; 567 savable_comment_add (p); 568 free (copy); 569} 570 571 572static void 573do_extract_glade (FILE *fp, 574 const char *real_filename, const char *logical_filename, 575 msgdomain_list_ty *mdlp) 576{ 577 mlp = mdlp->item[0]->messages; 578 579 /* expat feeds us strings in UTF-8 encoding. */ 580 xgettext_current_source_encoding = po_charset_utf8; 581 582 logical_file_name = xstrdup (logical_filename); 583 584 init_keywords (); 585 586 parser = XML_ParserCreate (NULL); 587 if (parser == NULL) 588 error (EXIT_FAILURE, 0, _("memory exhausted")); 589 590 XML_SetElementHandler (parser, start_element_handler, end_element_handler); 591 XML_SetCharacterDataHandler (parser, character_data_handler); 592 XML_SetCommentHandler (parser, comment_handler); 593 594 stack_depth = 0; 595 596 while (!feof (fp)) 597 { 598 char buf[4096]; 599 int count = fread (buf, 1, sizeof buf, fp); 600 601 if (count == 0) 602 { 603 if (ferror (fp)) 604 error (EXIT_FAILURE, errno, _("\ 605error while reading \"%s\""), real_filename); 606 /* EOF reached. */ 607 break; 608 } 609 610 if (XML_Parse (parser, buf, count, 0) == 0) 611 error (EXIT_FAILURE, 0, _("%s:%lu:%lu: %s"), logical_filename, 612 (unsigned long) XML_GetCurrentLineNumber (parser), 613 (unsigned long) XML_GetCurrentColumnNumber (parser) + 1, 614 XML_ErrorString (XML_GetErrorCode (parser))); 615 } 616 617 if (XML_Parse (parser, NULL, 0, 1) == 0) 618 error (EXIT_FAILURE, 0, _("%s:%lu:%lu: %s"), logical_filename, 619 (unsigned long) XML_GetCurrentLineNumber (parser), 620 (unsigned long) XML_GetCurrentColumnNumber (parser) + 1, 621 XML_ErrorString (XML_GetErrorCode (parser))); 622 623 XML_ParserFree (parser); 624 625 /* Close scanner. */ 626 logical_file_name = NULL; 627 parser = NULL; 628} 629 630#endif 631 632void 633extract_glade (FILE *fp, 634 const char *real_filename, const char *logical_filename, 635 flag_context_list_table_ty *flag_table, 636 msgdomain_list_ty *mdlp) 637{ 638#if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT 639 if (LIBEXPAT_AVAILABLE ()) 640 do_extract_glade (fp, real_filename, logical_filename, mdlp); 641 else 642#endif 643 { 644 multiline_error (xstrdup (""), 645 xasprintf (_("\ 646Language \"glade\" is not supported. %s relies on expat.\n\ 647This version was built without expat.\n"), 648 basename (program_name))); 649 exit (EXIT_FAILURE); 650 } 651} 652