Home | History | Annotate | Line # | Download | only in src
      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