Imported Upstream version 0.18.3.2
[platform/upstream/gettext.git] / gettext-tools / src / x-glade.c
1 /* xgettext glade backend.
2    Copyright (C) 2002-2003, 2005-2009, 2013 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 "error.h"
43 #include "xerror.h"
44 #include "xvasprintf.h"
45 #include "basename.h"
46 #include "progname.h"
47 #include "xalloc.h"
48 #include "hash.h"
49 #include "po-charset.h"
50 #include "gettext.h"
51
52 #define _(s) gettext(s)
53
54
55 /* Glade is an XML based format with three variants.  The syntax for
56    each format is defined as follows.
57
58    - Glade 1
59      Some example files are contained in libglade-0.16.
60
61    - Glade 2
62      See http://library.gnome.org/devel/libglade/unstable/libglade-dtd.html
63
64    - GtkBuilder
65      See https://developer.gnome.org/gtk3/stable/GtkBuilder.html#BUILDER-UI  */
66
67
68 /* ====================== Keyword set customization.  ====================== */
69
70 /* If true extract all strings.  */
71 static bool extract_all = false;
72
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;
78
79
80 void
81 x_glade_extract_all ()
82 {
83   extract_all = true;
84 }
85
86
87 void
88 x_glade_keyword (const char *name)
89 {
90   if (name == NULL)
91     default_keywords = false;
92   else
93     {
94       if (keywords.table == NULL)
95         hash_init (&keywords, 100);
96
97       hash_insert_entry (&keywords, name, strlen (name), NULL);
98     }
99 }
100
101 /* Finish initializing the keywords hash table.
102    Called after argument processing, before each file is processed.  */
103 static void
104 init_keywords ()
105 {
106   if (default_keywords)
107     {
108       /* When adding new keywords here, also update the documentation in
109          xgettext.texi!  */
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;
119     }
120 }
121
122
123 /* ======================= Different libexpat ABIs.  ======================= */
124
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.  */
135
136 #if !DYNLOAD_LIBEXPAT
137
138 # if XML_MAJOR_VERSION >= 2
139
140 /* expat >= 2.0 -> Return type is 'int64_t' worst-case.  */
141
142 /* Put the function pointers into variables, because some GCC 4 versions
143    generate an abort when we convert symbol address to different function
144    pointer types.  */
145 static void *p_XML_GetCurrentLineNumber = (void *) &XML_GetCurrentLineNumber;
146 static void *p_XML_GetCurrentColumnNumber = (void *) &XML_GetCurrentColumnNumber;
147
148 /* Return true if libexpat was compiled with -DXML_LARGE_SIZE.  */
149 static bool
150 is_XML_LARGE_SIZE_ABI (void)
151 {
152   static bool tested;
153   static bool is_large;
154
155   if (!tested)
156     {
157       const XML_Feature *features;
158
159       is_large = false;
160       for (features = XML_GetFeatureList (); features->name != NULL; features++)
161         if (strcmp (features->name, "XML_LARGE_SIZE") == 0)
162           {
163             is_large = true;
164             break;
165           }
166
167       tested = true;
168     }
169   return is_large;
170 }
171
172 static int64_t
173 GetCurrentLineNumber (XML_Parser parser)
174 {
175   if (is_XML_LARGE_SIZE_ABI ())
176     return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser);
177   else
178     return ((long (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser);
179 }
180 #  define XML_GetCurrentLineNumber GetCurrentLineNumber
181
182 static int64_t
183 GetCurrentColumnNumber (XML_Parser parser)
184 {
185   if (is_XML_LARGE_SIZE_ABI ())
186     return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser);
187   else
188     return ((long (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser);
189 }
190 #  define XML_GetCurrentColumnNumber GetCurrentColumnNumber
191
192 # else
193
194 /* expat < 2.0 -> Return type is 'int'.  */
195
196 # endif
197
198 #endif
199
200
201 /* ===================== Dynamic loading of libexpat.  ===================== */
202
203 #if DYNLOAD_LIBEXPAT
204
205 typedef struct
206   {
207     int major;
208     int minor;
209     int micro;
210   }
211   XML_Expat_Version;
212 enum XML_FeatureEnum { XML_FEATURE_END = 0 };
213 typedef struct
214   {
215     enum XML_FeatureEnum feature;
216     const char *name;
217     long int value;
218   }
219   XML_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);
228
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);
241
242 #define XML_ExpatVersionInfo (*p_XML_ExpatVersionInfo)
243 #define XML_GetFeatureList (*p_XML_GetFeatureList)
244
245 enum XML_Size_ABI { is_int, is_long, is_int64_t };
246
247 static enum XML_Size_ABI
248 get_XML_Size_ABI (void)
249 {
250   static bool tested;
251   static enum XML_Size_ABI abi;
252
253   if (!tested)
254     {
255       if (XML_ExpatVersionInfo () .major >= 2)
256         /* expat >= 2.0 -> XML_Size is 'int64_t' or 'long'.  */
257         {
258           const XML_Feature *features;
259
260           abi = is_long;
261           for (features = XML_GetFeatureList ();
262                features->name != NULL;
263                features++)
264             if (strcmp (features->name, "XML_LARGE_SIZE") == 0)
265               {
266                 abi = is_int64_t;
267                 break;
268               }
269         }
270       else
271         /* expat < 2.0 -> XML_Size is 'int'.  */
272         abi = is_int;
273       tested = true;
274     }
275   return abi;
276 }
277
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)
284
285 static int64_t
286 XML_GetCurrentLineNumber (XML_Parser parser)
287 {
288   switch (get_XML_Size_ABI ())
289     {
290     case is_int:
291       return ((int (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser);
292     case is_long:
293       return ((long (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser);
294     case is_int64_t:
295       return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentLineNumber) (parser);
296     default:
297       abort ();
298     }
299 }
300
301 static int64_t
302 XML_GetCurrentColumnNumber (XML_Parser parser)
303 {
304   switch (get_XML_Size_ABI ())
305     {
306     case is_int:
307       return ((int (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser);
308     case is_long:
309       return ((long (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser);
310     case is_int64_t:
311       return ((int64_t (*) (XML_Parser)) p_XML_GetCurrentColumnNumber) (parser);
312     default:
313       abort ();
314     }
315 }
316
317 #define XML_ParserFree (*p_XML_ParserFree)
318 #define XML_ErrorString (*p_XML_ErrorString)
319
320 static int libexpat_loaded = 0;
321
322 static bool
323 load_libexpat ()
324 {
325   if (libexpat_loaded == 0)
326     {
327       void *handle;
328
329       /* Try to load libexpat-2.x.  */
330       handle = dlopen ("libexpat.so.1", RTLD_LAZY);
331       if (handle == NULL)
332         /* Try to load libexpat-1.x.  */
333         handle = dlopen ("libexpat.so.0", RTLD_LAZY);
334       if (handle != NULL
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
353           && (p_XML_Parse =
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)
369         libexpat_loaded = 1;
370       else
371         libexpat_loaded = -1;
372     }
373   return libexpat_loaded >= 0;
374 }
375
376 #define LIBEXPAT_AVAILABLE() (load_libexpat ())
377
378 #elif HAVE_LIBEXPAT
379
380 #define LIBEXPAT_AVAILABLE() true
381
382 #endif
383
384 /* ============================= XML parsing.  ============================= */
385
386 #if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT
387
388 /* Accumulator for the extracted messages.  */
389 static message_list_ty *mlp;
390
391 /* Logical filename, used to label the extracted messages.  */
392 static char *logical_file_name;
393
394 /* XML parser.  */
395 static XML_Parser parser;
396
397 struct element_state
398 {
399   bool extract_string;
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 */
403   int lineno;
404   char *buffer;
405   size_t bufmax;
406   size_t buflen;
407 };
408 static struct element_state *stack;
409 static size_t stack_size;
410
411 /* Ensures stack_size >= size.  */
412 static void
413 ensure_stack_size (size_t size)
414 {
415   if (size > stack_size)
416     {
417       stack_size = 2 * stack_size;
418       if (stack_size < size)
419         stack_size = size;
420       stack =
421         (struct element_state *)
422         xrealloc (stack, stack_size * sizeof (struct element_state));
423     }
424 }
425
426 static size_t stack_depth;
427
428 /* Parser logic for each Glade compatible file format.  */
429 struct element_parser
430 {
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);
434 };
435 static struct element_parser *element_parser;
436
437 static void
438 start_element_null (struct element_state *p, const char *name,
439                     const char **attributes)
440 {
441 }
442
443 static void
444 end_element_null (struct element_state *p, const char *name)
445 {
446 }
447
448 static void
449 start_element_glade1 (struct element_state *p, const char *name,
450                       const char **attributes)
451 {
452   void *hash_result;
453
454   /* In Glade 1, a few specific elements are translatable.  */
455   if (!p->extract_string)
456     p->extract_string =
457       (hash_find_entry (&keywords, name, strlen (name), &hash_result) == 0);
458 }
459
460 static void
461 end_element_glade1 (struct element_state *p, const char *name)
462 {
463   lex_pos_ty pos;
464
465   pos.file_name = logical_file_name;
466   pos.line_number = p->lineno;
467
468   if (p->buffer != NULL)
469     {
470       remember_a_message (mlp, NULL, p->buffer,
471                           null_context, &pos,
472                           p->extracted_comment, savable_comment);
473       p->buffer = NULL;
474     }
475 }
476
477 static void
478 start_element_glade2 (struct element_state *p, const char *name,
479                       const char **attributes)
480 {
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))
490     {
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)
496         {
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);
503           attp += 2;
504         }
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)
510          : NULL);
511     }
512
513   /* In Glade 2, the attribute description="..." of <atkaction>
514      element is also translatable.  */
515   if (!p->extract_string
516       && strcmp (name, "atkaction") == 0)
517     {
518       const char **attp = attributes;
519       while (*attp != NULL)
520         {
521           if (strcmp (attp[0], "description") == 0)
522             {
523               if (strcmp (attp[1], "") != 0)
524                 {
525                   lex_pos_ty pos;
526
527                   pos.file_name = logical_file_name;
528                   pos.line_number = XML_GetCurrentLineNumber (parser);
529
530                   remember_a_message (mlp, NULL, xstrdup (attp[1]),
531                                       null_context, &pos,
532                                       NULL, savable_comment);
533                 }
534               break;
535             }
536           attp += 2;
537         }
538     }
539 }
540
541 static void
542 end_element_glade2 (struct element_state *p, const char *name)
543 {
544   lex_pos_ty pos;
545   char *msgid = NULL;
546   char *msgctxt = NULL;
547
548   pos.file_name = logical_file_name;
549   pos.line_number = p->lineno;
550
551   if (p->extract_context)
552     {
553       char *separator = strchr (p->buffer, '|');
554
555       if (separator == NULL)
556         {
557           error_with_progname = false;
558           error_at_line (0, 0,
559                          pos.file_name,
560                          pos.line_number,
561                          _("\
562 Missing context for the string extracted from '%s' element"),
563                          name);
564           error_with_progname = true;
565         }
566       else
567         {
568           *separator = '\0';
569           msgid = xstrdup (separator + 1);
570           msgctxt = xstrdup (p->buffer);
571         }
572     }
573   else
574     {
575       msgid = p->buffer;
576       p->buffer = NULL;
577     }
578
579   if (msgid != NULL)
580     remember_a_message (mlp, msgctxt, msgid,
581                         null_context, &pos,
582                         p->extracted_comment, savable_comment);
583 }
584
585 static void
586 start_element_gtkbuilder (struct element_state *p, const char *name,
587                           const char **attributes)
588 {
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)
595     {
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)
601         {
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];
608           attp += 2;
609         }
610       p->extract_string = has_translatable;
611       p->extracted_comment =
612         (has_translatable && extracted_comment != NULL
613          ? xstrdup (extracted_comment)
614          : NULL);
615       p->extracted_context =
616         (has_translatable && extracted_context != NULL
617          ? xstrdup (extracted_context)
618          : NULL); 
619    }
620 }
621
622 static void
623 end_element_gtkbuilder (struct element_state *p, const char *name)
624 {
625   lex_pos_ty pos;
626
627   pos.file_name = logical_file_name;
628   pos.line_number = p->lineno;
629
630   if (p->buffer != NULL)
631     {
632       remember_a_message (mlp, p->extracted_context, p->buffer,
633                           null_context, &pos,
634                           p->extracted_comment, savable_comment);
635       p->buffer = NULL;
636       p->extracted_context = NULL;
637     }
638 }
639
640 static struct element_parser element_parser_null =
641 {
642   start_element_null,
643   end_element_null
644 };
645
646 static struct element_parser element_parser_glade1 =
647 {
648   start_element_glade1,
649   end_element_glade1
650 };
651
652 static struct element_parser element_parser_glade2 =
653 {
654   start_element_glade2,
655   end_element_glade2
656 };
657
658 static struct element_parser element_parser_gtkbuilder =
659 {
660   start_element_gtkbuilder,
661   end_element_gtkbuilder
662 };
663
664 /* Callback called when <element> is seen.  */
665 static void
666 start_element_handler (void *userData, const char *name,
667                        const char **attributes)
668 {
669   struct element_state *p;
670
671   if (!stack_depth)
672     {
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;
679       else
680         {
681           element_parser = &element_parser_null;
682           error_with_progname = false;
683           error_at_line (0, 0,
684                          logical_file_name,
685                          XML_GetCurrentLineNumber (parser),
686                          _("\
687 The root element <%s> is not allowed in a valid Glade file"),
688                          name);
689           error_with_progname = true;
690         }
691     }
692
693   /* Increase stack depth.  */
694   stack_depth++;
695   ensure_stack_size (stack_depth + 1);
696
697   /* Don't extract a string for the containing element.  */
698   stack[stack_depth - 1].extract_string = false;
699
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;
705
706   element_parser->start_element (p, name, attributes);
707
708   p->lineno = XML_GetCurrentLineNumber (parser);
709   p->buffer = NULL;
710   p->bufmax = 0;
711   p->buflen = 0;
712   if (!p->extract_string)
713     savable_comment_reset ();
714 }
715
716 /* Callback called when </element> is seen.  */
717 static void
718 end_element_handler (void *userData, const char *name)
719 {
720   struct element_state *p = &stack[stack_depth];
721
722   /* Actually extract string.  */
723   if (p->extract_string)
724     {
725       /* Don't extract the empty string.  */
726       if (p->buflen > 0)
727         {
728           if (p->buflen == p->bufmax)
729             p->buffer = (char *) xrealloc (p->buffer, p->buflen + 1);
730           p->buffer[p->buflen] = '\0';
731
732           element_parser->end_element (p, name);
733         }
734     }
735
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)
742     free (p->buffer);
743
744   /* Decrease stack depth.  */
745   stack_depth--;
746
747   savable_comment_reset ();
748 }
749
750 /* Callback called when some text is seen.  */
751 static void
752 character_data_handler (void *userData, const char *s, int len)
753 {
754   struct element_state *p = &stack[stack_depth];
755
756   /* Accumulate character data.  */
757   if (len > 0)
758     {
759       if (p->buflen + len > p->bufmax)
760         {
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);
765         }
766       memcpy (p->buffer + p->buflen, s, len);
767       p->buflen += len;
768     }
769 }
770
771 /* Callback called when some comment text is seen.  */
772 static void
773 comment_handler (void *userData, const char *data)
774 {
775   /* Split multiline comment into lines, and remove leading and trailing
776      whitespace.  */
777   char *copy = xstrdup (data);
778   char *p;
779   char *q;
780
781   for (p = copy; (q = strchr (p, '\n')) != NULL; p = q + 1)
782     {
783       while (p[0] == ' ' || p[0] == '\t')
784         p++;
785       while (q > p && (q[-1] == ' ' || q[-1] == '\t'))
786         q--;
787       *q = '\0';
788       savable_comment_add (p);
789     }
790   q = p + strlen (p);
791   while (p[0] == ' ' || p[0] == '\t')
792     p++;
793   while (q > p && (q[-1] == ' ' || q[-1] == '\t'))
794     q--;
795   *q = '\0';
796   savable_comment_add (p);
797   free (copy);
798 }
799
800
801 static void
802 do_extract_glade (FILE *fp,
803                   const char *real_filename, const char *logical_filename,
804                   msgdomain_list_ty *mdlp)
805 {
806   mlp = mdlp->item[0]->messages;
807
808   /* expat feeds us strings in UTF-8 encoding.  */
809   xgettext_current_source_encoding = po_charset_utf8;
810
811   logical_file_name = xstrdup (logical_filename);
812
813   init_keywords ();
814
815   parser = XML_ParserCreate (NULL);
816   if (parser == NULL)
817     error (EXIT_FAILURE, 0, _("memory exhausted"));
818
819   XML_SetElementHandler (parser, start_element_handler, end_element_handler);
820   XML_SetCharacterDataHandler (parser, character_data_handler);
821   XML_SetCommentHandler (parser, comment_handler);
822
823   stack_depth = 0;
824   element_parser = &element_parser_null;
825
826   while (!feof (fp))
827     {
828       char buf[4096];
829       int count = fread (buf, 1, sizeof buf, fp);
830
831       if (count == 0)
832         {
833           if (ferror (fp))
834             error (EXIT_FAILURE, errno, _("\
835 error while reading \"%s\""), real_filename);
836           /* EOF reached.  */
837           break;
838         }
839
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)));
845     }
846
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)));
852
853   XML_ParserFree (parser);
854
855   /* Close scanner.  */
856   logical_file_name = NULL;
857   parser = NULL;
858 }
859
860 #endif
861
862 void
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)
867 {
868 #if DYNLOAD_LIBEXPAT || HAVE_LIBEXPAT
869   if (LIBEXPAT_AVAILABLE ())
870     do_extract_glade (fp, real_filename, logical_filename, mdlp);
871   else
872 #endif
873     {
874       multiline_error (xstrdup (""),
875                        xasprintf (_("\
876 Language \"glade\" is not supported. %s relies on expat.\n\
877 This version was built without expat.\n"),
878                                   basename (program_name)));
879       exit (EXIT_FAILURE);
880     }
881 }