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. */
61 static bool extract_all = false;
62
63 static hash_table keywords;
64 static bool default_keywords = true;
65
66
67 void
x_glade_extract_all()68 x_glade_extract_all ()
69 {
70 extract_all = true;
71 }
72
73
74 void
x_glade_keyword(const char * name)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
init_keywords()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
load_libexpat()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
ensure_stack_size(size_t size)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
start_element_handler(void * userData,const char * name,const char ** attributes)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
end_element_handler(void * userData,const char * name)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
character_data_handler(void * userData,const char * s,int len)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
comment_handler(void * userData,const char * data)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
do_extract_glade(FILE * fp,const char * real_filename,const char * logical_filename,msgdomain_list_ty * mdlp)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
extract_glade(FILE * fp,const char * real_filename,const char * logical_filename,flag_context_list_table_ty * flag_table,msgdomain_list_ty * mdlp)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