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@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.  */
61static bool extract_all = false;
62
63static hash_table keywords;
64static bool default_keywords = true;
65
66
67void
68x_glade_extract_all ()
69{
70  extract_all = true;
71}
72
73
74void
75x_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.  */
90static void
91init_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
114typedef void *XML_Parser;
115typedef char XML_Char;
116typedef char XML_LChar;
117enum XML_Error { XML_ERROR_NONE };
118typedef void (*XML_StartElementHandler) (void *userData, const XML_Char *name, const XML_Char **atts);
119typedef void (*XML_EndElementHandler) (void *userData, const XML_Char *name);
120typedef void (*XML_CharacterDataHandler) (void *userData, const XML_Char *s, int len);
121typedef void (*XML_CommentHandler) (void *userData, const XML_Char *data);
122
123static XML_Parser (*p_XML_ParserCreate) (const XML_Char *encoding);
124static void (*p_XML_SetElementHandler) (XML_Parser parser, XML_StartElementHandler start, XML_EndElementHandler end);
125static void (*p_XML_SetCharacterDataHandler) (XML_Parser parser, XML_CharacterDataHandler handler);
126static void (*p_XML_SetCommentHandler) (XML_Parser parser, XML_CommentHandler handler);
127static int (*p_XML_Parse) (XML_Parser parser, const char *s, int len, int isFinal);
128static enum XML_Error (*p_XML_GetErrorCode) (XML_Parser parser);
129#if XML_MAJOR_VERSION >= 2
130static XML_Size (*p_XML_GetCurrentLineNumber) (XML_Parser parser);
131static XML_Size (*p_XML_GetCurrentColumnNumber) (XML_Parser parser);
132#else
133static int (*p_XML_GetCurrentLineNumber) (XML_Parser parser);
134static int (*p_XML_GetCurrentColumnNumber) (XML_Parser parser);
135#endif
136static void (*p_XML_ParserFree) (XML_Parser parser);
137static 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
150static int libexpat_loaded = 0;
151
152static bool
153load_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.  */
196static message_list_ty *mlp;
197
198/* Logical filename, used to label the extracted messages.  */
199static char *logical_file_name;
200
201/* XML parser.  */
202static XML_Parser parser;
203
204struct element_state
205{
206  bool extract_string;
207  int lineno;
208  char *buffer;
209  size_t bufmax;
210  size_t buflen;
211};
212static struct element_state *stack;
213static size_t stack_size;
214
215/* Ensures stack_size >= size.  */
216static void
217ensure_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
230static size_t stack_depth;
231
232/* Callback called when <element> is seen.  */
233static void
234start_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.  */
303static void
304end_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.  */
340static void
341character_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.  */
361static void
362comment_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
390static void
391do_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, _("\
423error 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
450void
451extract_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 (_("\
464Language \"glade\" is not supported. %s relies on expat.\n\
465This version was built without expat.\n"),
466				  basename (program_name)));
467      exit (EXIT_FAILURE);
468    }
469}
470