1 /* xgettext glade backend.
2 Copyright (C) 2002-2003, 2005-2009, 2013 Free Software Foundation, Inc.
4 This file was written by Bruno Haible <haible@clisp.cons.org>, 2002.
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.
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.
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/>. */
44 #include "xvasprintf.h"
49 #include "po-charset.h"
52 #define _(s) gettext(s)
55 /* Glade is an XML based format with three variants. The syntax for
56 each format is defined as follows.
59 Some example files are contained in libglade-0.16.
62 See http://library.gnome.org/devel/libglade/unstable/libglade-dtd.html
65 See https://developer.gnome.org/gtk3/stable/GtkBuilder.html#BUILDER-UI */
68 /* ====================== Keyword set customization. ====================== */
70 /* If true extract all strings. */
71 static bool extract_all = false;
73 /* The keywords correspond to the translatable elements in Glade 1.
74 For Glade 2 and GtkBuilder, translatable content is determined by
75 the translatable="..." attribute, thus those keywords are not used. */
76 static hash_table keywords;
77 static bool default_keywords = true;
81 x_glade_extract_all ()
88 x_glade_keyword (const char *name)
91 default_keywords = false;
94 if (keywords.table == NULL)
95 hash_init (&keywords, 100);
97 hash_insert_entry (&keywords, name, strlen (name), NULL);
101 /* Finish initializing the keywords hash table.
102 Called after argument processing, before each file is processed. */
106 if (default_keywords)
108 /* When adding new keywords here, also update the documentation in
110 x_glade_keyword ("label");
111 x_glade_keyword ("title");
112 x_glade_keyword ("text");
113 x_glade_keyword ("format");
114 x_glade_keyword ("copyright");
115 x_glade_keyword ("comments");
116 x_glade_keyword ("preview_text");
117 x_glade_keyword ("tooltip");
118 default_keywords = false;
123 /* ======================= Different libexpat ABIs. ======================= */
125 /* There are three different ABIs of libexpat, regarding the functions
126 XML_GetCurrentLineNumber and XML_GetCurrentColumnNumber.
127 In expat < 2.0, they return an 'int'.
128 In expat >= 2.0, they return
129 - a 'long' if expat was compiled with the default flags, or
130 - a 'long long' if expat was compiled with -DXML_LARGE_SIZE.
131 But the <expat.h> include file does not contain the information whether
132 expat was compiled with -DXML_LARGE_SIZE; so the include file is lying!
133 For this information, we need to call XML_GetFeatureList(), for
134 expat >= 2.0.1; for expat = 2.0.0, we have to assume the default flags. */
136 #if !DYNLOAD_LIBEXPAT
138 # if XML_MAJOR_VERSION >= 2
140 /* expat >= 2.0 -> Return type is 'int64_t' worst-case. */
142 /* Put the function pointers into variables, because some GCC 4 versions
143 generate an abort when we convert symbol address to different function
145 static void *p_XML_GetCurrentLineNumber = (void *) &XML_GetCurrentLineNumber;
146 static void *p_XML_GetCurrentColumnNumber = (void *) &XML_GetCurrentColumnNumber;
148 /* Return true if libexpat was compiled with -DXML_LARGE_SIZE. */
150 is_XML_LARGE_SIZE_ABI (void)
153 static bool is_large;
157 const XML_Feature *features;
160 for (features = XML_GetFeatureList (); features->name != NULL; features++)
161 if (strcmp (features->name, "XML_LARGE_SIZE") == 0)
173 GetCurrentLineNumber (XML_Parser parser)
175 if (is_XML_LARGE_SIZE_ABI ())
176 return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser);
178 return ((long (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser);
180 # define XML_GetCurrentLineNumber GetCurrentLineNumber
183 GetCurrentColumnNumber (XML_Parser parser)
185 if (is_XML_LARGE_SIZE_ABI ())
186 return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser);
188 return ((long (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser);
190 # define XML_GetCurrentColumnNumber GetCurrentColumnNumber
194 /* expat < 2.0 -> Return type is 'int'. */
201 /* ===================== Dynamic loading of libexpat. ===================== */
212 enum XML_FeatureEnum { XML_FEATURE_END = 0 };
215 enum XML_FeatureEnum feature;
220 typedef void *XML_Parser;
221 typedef char XML_Char;
222 typedef char XML_LChar;
223 enum XML_Error { XML_ERROR_NONE };
224 typedef void (*XML_StartElementHandler) (void *userData, const XML_Char *name, const XML_Char **atts);
225 typedef void (*XML_EndElementHandler) (void *userData, const XML_Char *name);
226 typedef void (*XML_CharacterDataHandler) (void *userData, const XML_Char *s, int len);
227 typedef void (*XML_CommentHandler) (void *userData, const XML_Char *data);
229 static XML_Expat_Version (*p_XML_ExpatVersionInfo) (void);
230 static const XML_Feature * (*p_XML_GetFeatureList) (void);
231 static XML_Parser (*p_XML_ParserCreate) (const XML_Char *encoding);
232 static void (*p_XML_SetElementHandler) (XML_Parser parser, XML_StartElementHandler start, XML_EndElementHandler end);
233 static void (*p_XML_SetCharacterDataHandler) (XML_Parser parser, XML_CharacterDataHandler handler);
234 static void (*p_XML_SetCommentHandler) (XML_Parser parser, XML_CommentHandler handler);
235 static int (*p_XML_Parse) (XML_Parser parser, const char *s, int len, int isFinal);
236 static enum XML_Error (*p_XML_GetErrorCode) (XML_Parser parser);
237 static void *p_XML_GetCurrentLineNumber;
238 static void *p_XML_GetCurrentColumnNumber;
239 static void (*p_XML_ParserFree) (XML_Parser parser);
240 static const XML_LChar * (*p_XML_ErrorString) (int code);
242 #define XML_ExpatVersionInfo (*p_XML_ExpatVersionInfo)
243 #define XML_GetFeatureList (*p_XML_GetFeatureList)
245 enum XML_Size_ABI { is_int, is_long, is_int64_t };
247 static enum XML_Size_ABI
248 get_XML_Size_ABI (void)
251 static enum XML_Size_ABI abi;
255 if (XML_ExpatVersionInfo () .major >= 2)
256 /* expat >= 2.0 -> XML_Size is 'int64_t' or 'long'. */
258 const XML_Feature *features;
261 for (features = XML_GetFeatureList ();
262 features->name != NULL;
264 if (strcmp (features->name, "XML_LARGE_SIZE") == 0)
271 /* expat < 2.0 -> XML_Size is 'int'. */
278 #define XML_ParserCreate (*p_XML_ParserCreate)
279 #define XML_SetElementHandler (*p_XML_SetElementHandler)
280 #define XML_SetCharacterDataHandler (*p_XML_SetCharacterDataHandler)
281 #define XML_SetCommentHandler (*p_XML_SetCommentHandler)
282 #define XML_Parse (*p_XML_Parse)
283 #define XML_GetErrorCode (*p_XML_GetErrorCode)
286 XML_GetCurrentLineNumber (XML_Parser parser)
288 switch (get_XML_Size_ABI ())
291 return ((int (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser);
293 return ((long (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser);
295 return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser);
302 XML_GetCurrentColumnNumber (XML_Parser parser)
304 switch (get_XML_Size_ABI ())
307 return ((int (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser);
309 return ((long (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser);
311 return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser);
317 #define XML_ParserFree (*p_XML_ParserFree)
318 #define XML_ErrorString (*p_XML_ErrorString)
320 static int libexpat_loaded = 0;
325 if (libexpat_loaded == 0)
329 /* Try to load libexpat-2.x. */
330 handle = dlopen ("libexpat.so.1", RTLD_LAZY);
332 /* Try to load libexpat-1.x. */
333 handle = dlopen ("libexpat.so.0", RTLD_LAZY);
335 && (p_XML_ExpatVersionInfo =
336 (XML_Expat_Version (*) (void))
337 dlsym (handle, "XML_ExpatVersionInfo")) != NULL
338 && (p_XML_GetFeatureList =
339 (const XML_Feature * (*) (void))
340 dlsym (handle, "XML_GetFeatureList")) != NULL
341 && (p_XML_ParserCreate =
342 (XML_Parser (*) (const XML_Char *))
343 dlsym (handle, "XML_ParserCreate")) != NULL
344 && (p_XML_SetElementHandler =
345 (void (*) (XML_Parser, XML_StartElementHandler, XML_EndElementHandler))
346 dlsym (handle, "XML_SetElementHandler")) != NULL
347 && (p_XML_SetCharacterDataHandler =
348 (void (*) (XML_Parser, XML_CharacterDataHandler))
349 dlsym (handle, "XML_SetCharacterDataHandler")) != NULL
350 && (p_XML_SetCommentHandler =
351 (void (*) (XML_Parser, XML_CommentHandler))
352 dlsym (handle, "XML_SetCommentHandler")) != NULL
354 (int (*) (XML_Parser, const char *, int, int))
355 dlsym (handle, "XML_Parse")) != NULL
356 && (p_XML_GetErrorCode =
357 (enum XML_Error (*) (XML_Parser))
358 dlsym (handle, "XML_GetErrorCode")) != NULL
359 && (p_XML_GetCurrentLineNumber =
360 dlsym (handle, "XML_GetCurrentLineNumber")) != NULL
361 && (p_XML_GetCurrentColumnNumber =
362 dlsym (handle, "XML_GetCurrentColumnNumber")) != NULL
363 && (p_XML_ParserFree =
364 (void (*) (XML_Parser))
365 dlsym (handle, "XML_ParserFree")) != NULL
366 && (p_XML_ErrorString =
367 (const XML_LChar * (*) (int))
368 dlsym (handle, "XML_ErrorString")) != NULL)
371 libexpat_loaded = -1;
373 return libexpat_loaded >= 0;
376 #define LIBEXPAT_AVAILABLE() (load_libexpat ())
380 #define LIBEXPAT_AVAILABLE() true
384 /* ============================= XML parsing. ============================= */
386 #if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT
388 /* Accumulator for the extracted messages. */
389 static message_list_ty *mlp;
391 /* Logical filename, used to label the extracted messages. */
392 static char *logical_file_name;
395 static XML_Parser parser;
400 bool extract_context; /* used by Glade 2 */
401 char *extracted_comment; /* used by Glade 2 or GtkBuilder */
402 char *extracted_context; /* used by GtkBuilder */
408 static struct element_state *stack;
409 static size_t stack_size;
411 /* Ensures stack_size >= size. */
413 ensure_stack_size (size_t size)
415 if (size > stack_size)
417 stack_size = 2 * stack_size;
418 if (stack_size < size)
421 (struct element_state *)
422 xrealloc (stack, stack_size * sizeof (struct element_state));
426 static size_t stack_depth;
428 /* Parser logic for each Glade compatible file format. */
429 struct element_parser
431 void (*start_element) (struct element_state *p, const char *name,
432 const char **attributes);
433 void (*end_element) (struct element_state *p, const char *name);
435 static struct element_parser *element_parser;
438 start_element_null (struct element_state *p, const char *name,
439 const char **attributes)
444 end_element_null (struct element_state *p, const char *name)
449 start_element_glade1 (struct element_state *p, const char *name,
450 const char **attributes)
454 /* In Glade 1, a few specific elements are translatable. */
455 if (!p->extract_string)
457 (hash_find_entry (&keywords, name, strlen (name), &hash_result) == 0);
461 end_element_glade1 (struct element_state *p, const char *name)
465 pos.file_name = logical_file_name;
466 pos.line_number = p->lineno;
468 if (p->buffer != NULL)
470 remember_a_message (mlp, NULL, p->buffer,
472 p->extracted_comment, savable_comment);
478 start_element_glade2 (struct element_state *p, const char *name,
479 const char **attributes)
481 /* In Glade 2, all <property> and <atkproperty> elements are translatable
482 that have the attribute translatable="yes".
483 See <http://library.gnome.org/devel/libglade/unstable/libglade-dtd.html>.
484 The translator comment is found in the attribute comments="...".
485 See <http://live.gnome.org/TranslationProject/DevGuidelines/Use comments>.
486 If the element has the attribute context="yes", the content of
487 the element is in the form "msgctxt|msgid". */
488 if (!p->extract_string
489 && (strcmp (name, "property") == 0 || strcmp (name, "atkproperty") == 0))
491 bool has_translatable = false;
492 bool has_context = false;
493 const char *extracted_comment = NULL;
494 const char **attp = attributes;
495 while (*attp != NULL)
497 if (strcmp (attp[0], "translatable") == 0)
498 has_translatable = (strcmp (attp[1], "yes") == 0);
499 else if (strcmp (attp[0], "comments") == 0)
500 extracted_comment = attp[1];
501 else if (strcmp (attp[0], "context") == 0)
502 has_context = (strcmp (attp[1], "yes") == 0);
505 p->extract_string = has_translatable;
506 p->extract_context = has_context;
507 p->extracted_comment =
508 (has_translatable && extracted_comment != NULL
509 ? xstrdup (extracted_comment)
513 /* In Glade 2, the attribute description="..." of <atkaction>
514 element is also translatable. */
515 if (!p->extract_string
516 && strcmp (name, "atkaction") == 0)
518 const char **attp = attributes;
519 while (*attp != NULL)
521 if (strcmp (attp[0], "description") == 0)
523 if (strcmp (attp[1], "") != 0)
527 pos.file_name = logical_file_name;
528 pos.line_number = XML_GetCurrentLineNumber (parser);
530 remember_a_message (mlp, NULL, xstrdup (attp[1]),
532 NULL, savable_comment);
542 end_element_glade2 (struct element_state *p, const char *name)
546 char *msgctxt = NULL;
548 pos.file_name = logical_file_name;
549 pos.line_number = p->lineno;
551 if (p->extract_context)
553 char *separator = strchr (p->buffer, '|');
555 if (separator == NULL)
557 error_with_progname = false;
562 Missing context for the string extracted from '%s' element"),
564 error_with_progname = true;
569 msgid = xstrdup (separator + 1);
570 msgctxt = xstrdup (p->buffer);
580 remember_a_message (mlp, msgctxt, msgid,
582 p->extracted_comment, savable_comment);
586 start_element_gtkbuilder (struct element_state *p, const char *name,
587 const char **attributes)
589 /* In GtkBuilder (used by Glade 3), all elements are translatable
590 that have the attribute translatable="yes".
591 See <https://developer.gnome.org/gtk3/stable/GtkBuilder.html#BUILDER-UI>.
592 The translator comment is found in the attribute comments="..."
593 and context is found in the attribute context="...". */
594 if (!p->extract_string)
596 bool has_translatable = false;
597 const char *extracted_comment = NULL;
598 const char *extracted_context = NULL;
599 const char **attp = attributes;
600 while (*attp != NULL)
602 if (strcmp (attp[0], "translatable") == 0)
603 has_translatable = (strcmp (attp[1], "yes") == 0);
604 else if (strcmp (attp[0], "comments") == 0)
605 extracted_comment = attp[1];
606 else if (strcmp (attp[0], "context") == 0)
607 extracted_context = attp[1];
610 p->extract_string = has_translatable;
611 p->extracted_comment =
612 (has_translatable && extracted_comment != NULL
613 ? xstrdup (extracted_comment)
615 p->extracted_context =
616 (has_translatable && extracted_context != NULL
617 ? xstrdup (extracted_context)
623 end_element_gtkbuilder (struct element_state *p, const char *name)
627 pos.file_name = logical_file_name;
628 pos.line_number = p->lineno;
630 if (p->buffer != NULL)
632 remember_a_message (mlp, p->extracted_context, p->buffer,
634 p->extracted_comment, savable_comment);
636 p->extracted_context = NULL;
640 static struct element_parser element_parser_null =
646 static struct element_parser element_parser_glade1 =
648 start_element_glade1,
652 static struct element_parser element_parser_glade2 =
654 start_element_glade2,
658 static struct element_parser element_parser_gtkbuilder =
660 start_element_gtkbuilder,
661 end_element_gtkbuilder
664 /* Callback called when <element> is seen. */
666 start_element_handler (void *userData, const char *name,
667 const char **attributes)
669 struct element_state *p;
673 if (strcmp (name, "GTK-Interface") == 0)
674 element_parser = &element_parser_glade1;
675 else if (strcmp (name, "glade-interface") == 0)
676 element_parser = &element_parser_glade2;
677 else if (strcmp (name, "interface") == 0)
678 element_parser = &element_parser_gtkbuilder;
681 element_parser = &element_parser_null;
682 error_with_progname = false;
685 XML_GetCurrentLineNumber (parser),
687 The root element <%s> is not allowed in a valid Glade file"),
689 error_with_progname = true;
693 /* Increase stack depth. */
695 ensure_stack_size (stack_depth + 1);
697 /* Don't extract a string for the containing element. */
698 stack[stack_depth - 1].extract_string = false;
700 p = &stack[stack_depth];
701 p->extract_string = extract_all;
702 p->extract_context = false;
703 p->extracted_comment = NULL;
704 p->extracted_context = NULL;
706 element_parser->start_element (p, name, attributes);
708 p->lineno = XML_GetCurrentLineNumber (parser);
712 if (!p->extract_string)
713 savable_comment_reset ();
716 /* Callback called when </element> is seen. */
718 end_element_handler (void *userData, const char *name)
720 struct element_state *p = &stack[stack_depth];
722 /* Actually extract string. */
723 if (p->extract_string)
725 /* Don't extract the empty string. */
728 if (p->buflen == p->bufmax)
729 p->buffer = (char *) xrealloc (p->buffer, p->buflen + 1);
730 p->buffer[p->buflen] = '\0';
732 element_parser->end_element (p, name);
736 /* Free memory for this stack level. */
737 if (p->extracted_comment != NULL)
738 free (p->extracted_comment);
739 if (p->extracted_context != NULL)
740 free (p->extracted_context);
741 if (p->buffer != NULL)
744 /* Decrease stack depth. */
747 savable_comment_reset ();
750 /* Callback called when some text is seen. */
752 character_data_handler (void *userData, const char *s, int len)
754 struct element_state *p = &stack[stack_depth];
756 /* Accumulate character data. */
759 if (p->buflen + len > p->bufmax)
761 p->bufmax = 2 * p->bufmax;
762 if (p->bufmax < p->buflen + len)
763 p->bufmax = p->buflen + len;
764 p->buffer = (char *) xrealloc (p->buffer, p->bufmax);
766 memcpy (p->buffer + p->buflen, s, len);
771 /* Callback called when some comment text is seen. */
773 comment_handler (void *userData, const char *data)
775 /* Split multiline comment into lines, and remove leading and trailing
777 char *copy = xstrdup (data);
781 for (p = copy; (q = strchr (p, '\n')) != NULL; p = q + 1)
783 while (p[0] == ' ' || p[0] == '\t')
785 while (q > p && (q[-1] == ' ' || q[-1] == '\t'))
788 savable_comment_add (p);
791 while (p[0] == ' ' || p[0] == '\t')
793 while (q > p && (q[-1] == ' ' || q[-1] == '\t'))
796 savable_comment_add (p);
802 do_extract_glade (FILE *fp,
803 const char *real_filename, const char *logical_filename,
804 msgdomain_list_ty *mdlp)
806 mlp = mdlp->item[0]->messages;
808 /* expat feeds us strings in UTF-8 encoding. */
809 xgettext_current_source_encoding = po_charset_utf8;
811 logical_file_name = xstrdup (logical_filename);
815 parser = XML_ParserCreate (NULL);
817 error (EXIT_FAILURE, 0, _("memory exhausted"));
819 XML_SetElementHandler (parser, start_element_handler, end_element_handler);
820 XML_SetCharacterDataHandler (parser, character_data_handler);
821 XML_SetCommentHandler (parser, comment_handler);
824 element_parser = &element_parser_null;
829 int count = fread (buf, 1, sizeof buf, fp);
834 error (EXIT_FAILURE, errno, _("\
835 error while reading \"%s\""), real_filename);
840 if (XML_Parse (parser, buf, count, 0) == 0)
841 error (EXIT_FAILURE, 0, _("%s:%lu:%lu: %s"), logical_filename,
842 (unsigned long) XML_GetCurrentLineNumber (parser),
843 (unsigned long) XML_GetCurrentColumnNumber (parser) + 1,
844 XML_ErrorString (XML_GetErrorCode (parser)));
847 if (XML_Parse (parser, NULL, 0, 1) == 0)
848 error (EXIT_FAILURE, 0, _("%s:%lu:%lu: %s"), logical_filename,
849 (unsigned long) XML_GetCurrentLineNumber (parser),
850 (unsigned long) XML_GetCurrentColumnNumber (parser) + 1,
851 XML_ErrorString (XML_GetErrorCode (parser)));
853 XML_ParserFree (parser);
856 logical_file_name = NULL;
863 extract_glade (FILE *fp,
864 const char *real_filename, const char *logical_filename,
865 flag_context_list_table_ty *flag_table,
866 msgdomain_list_ty *mdlp)
868 #if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT
869 if (LIBEXPAT_AVAILABLE ())
870 do_extract_glade (fp, real_filename, logical_filename, mdlp);
874 multiline_error (xstrdup (""),
876 Language \"glade\" is not supported. %s relies on expat.\n\
877 This version was built without expat.\n"),
878 basename (program_name)));