]> git.sesse.net Git - vlc/blob - modules/misc/xml/xtag.c
955037cb5e0c85bf209862893633f9dddfe53ce5
[vlc] / modules / misc / xml / xtag.c
1 /*****************************************************************************
2  * xtag.c : a trivial parser for XML-like tags
3  *****************************************************************************
4  * Copyright (C) 2003-2004 Commonwealth Scientific and Industrial Research
5  *                         Organisation (CSIRO) Australia
6  * Copyright (C) 2000-2004 the VideoLAN team
7  *
8  * $Id$
9  *
10  * Authors: Conrad Parker <Conrad.Parker@csiro.au>
11  *          Andre Pang <Andre.Pang@csiro.au>
12  *          Gildas Bazin <gbazin@videolan.org>
13  *
14  * This program is free software; you can redistribute it and/or modify
15  * it under the terms of the GNU General Public License as published by
16  * the Free Software Foundation; either version 2 of the License, or
17  * (at your option) any later version.
18  *
19  * This program is distributed in the hope that it will be useful,
20  * but WITHOUT ANY WARRANTY; without even the implied warranty of
21  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22  * GNU General Public License for more details.
23  *
24  * You should have received a copy of the GNU General Public License
25  * along with this program; if not, write to the Free Software
26  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
27  *****************************************************************************/
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
35
36 #include <vlc_xml.h>
37 #include <vlc_block.h>
38 #include <vlc_stream.h>
39 #include <vlc_memory.h>
40
41 #include <ctype.h>
42 #include <stdarg.h>
43
44 #undef XTAG_DEBUG
45
46 typedef struct _XList
47 {
48     struct _XList *prev;
49     struct _XList *next;
50     void *data;
51 } XList;
52
53 /*
54  * struct XTag is kind of a union ... it normally represents a whole
55  * tag (and its children), but it could alternatively represent some
56  * PCDATA. Basically, if tag->pcdata is non-NULL, interpret only it and
57  * ignore the name, attributes and inner_tags.
58  */
59 typedef struct _XTag
60 {
61     char *name;
62     char *pcdata;
63     struct _XTag *parent;
64     XList *attributes;
65     XList *children;
66     XList *current_child;
67 } XTag;
68
69 typedef struct _XAttribute
70 {
71     char *name;
72     char *value;
73 } XAttribute;
74
75 typedef struct _XTagParser
76 {
77     int valid; /* boolean */
78     XTag *current_tag;
79     char *start;
80     char *end;
81 } XTagParser;
82
83 /*****************************************************************************
84  * Module descriptor
85  *****************************************************************************/
86 static int ReaderOpen( vlc_object_t * );
87 static void ReaderClose( vlc_object_t * );
88
89 vlc_module_begin ()
90     set_description( N_("Simple XML Parser") )
91     set_capability( "xml reader", 5 )
92     set_callbacks( ReaderOpen, ReaderClose )
93 vlc_module_end ()
94
95 struct xml_reader_sys_t
96 {
97     XTag *p_root; /* Root tag */
98     XTag *p_curtag; /* Current tag */
99     XList *p_curattr; /* Current attribute */
100     bool b_endtag;
101 };
102
103 static int ReaderRead( xml_reader_t * );
104 static int ReaderNodeType( xml_reader_t * );
105 static char *ReaderName( xml_reader_t * );
106 static char *ReaderValue( xml_reader_t * );
107 static int ReaderNextAttr( xml_reader_t * );
108
109 static int ReaderUseDTD ( xml_reader_t *, bool );
110
111 static XTag *xtag_new_parse( const char *, int );
112 static char *xtag_get_name( XTag * );
113 #if 0
114 static char *xtag_get_pcdata( XTag * );
115 static char *xtag_get_attribute( XTag *, char * );
116 #endif
117 static XTag *xtag_first_child( XTag *, char * );
118 static XTag *xtag_next_child( XTag *, char * );
119 static void  xtag_free( XTag * );
120 #if 0
121 static int xtag_snprint( char *, int, XTag * );
122 #endif
123
124 /*****************************************************************************
125  * Reader functions
126  *****************************************************************************/
127 static int ReaderOpen( vlc_object_t *p_this )
128 {
129     xml_reader_t *p_reader = (xml_reader_t *)p_this;
130     stream_t *s = p_reader->p_stream;
131     char *p_buffer;
132     int i_size, i_pos = 0, i_buffer = 2048;
133     XTag *p_root;
134
135     /* Open and read file */
136     p_buffer = malloc( i_buffer );
137     if( p_buffer == NULL )
138         return VLC_ENOMEM;
139
140     while( ( i_size = stream_Read( s, &p_buffer[i_pos], 2048 ) ) == 2048 )
141     {
142         i_pos += i_size;
143         i_buffer += i_size;
144         p_buffer = realloc_or_free( p_buffer, i_buffer );
145         if( !p_buffer )
146             return VLC_ENOMEM;
147     }
148     if( i_pos + i_size == 0 )
149     {
150         msg_Dbg( p_this, "empty XML" );
151         free( p_buffer );
152         return VLC_ENOMEM;
153     }
154     p_buffer[ i_pos + i_size ] = '\0'; /* 0 terminated string */
155
156     p_root = xtag_new_parse( p_buffer, i_buffer );
157     free( p_buffer );
158     if( !p_root )
159     {
160         msg_Warn( p_this, "couldn't parse XML" );
161         return VLC_ENOMEM;
162     }
163
164     p_reader->p_sys = malloc( sizeof(xml_reader_sys_t) );
165     if( !p_reader->p_sys )
166     {
167         xtag_free( p_root );
168         return VLC_ENOMEM;
169     }
170     p_reader->p_sys->p_root = p_root;
171     p_reader->p_sys->p_curtag = NULL;
172     p_reader->p_sys->p_curattr = NULL;
173     p_reader->p_sys->b_endtag = false;
174
175     p_reader->pf_read = ReaderRead;
176     p_reader->pf_node_type = ReaderNodeType;
177     p_reader->pf_name = ReaderName;
178     p_reader->pf_value = ReaderValue;
179     p_reader->pf_next_attr = ReaderNextAttr;
180     p_reader->pf_use_dtd = ReaderUseDTD;
181
182     return VLC_SUCCESS;
183 }
184
185 static void ReaderClose( vlc_object_t *p_this )
186 {
187     xml_reader_t *p_reader = (vlc_object_t *)p_this;
188
189     xtag_free( p_reader->p_sys->p_root );
190     free( p_reader->p_sys );
191 }
192
193 static int ReaderUseDTD ( xml_reader_t *p_reader, bool b_use )
194 {
195     VLC_UNUSED(p_reader); VLC_UNUSED(b_use);
196     return VLC_EGENERIC;
197 }
198
199 static int ReaderRead( xml_reader_t *p_reader )
200 {
201     XTag *p_child;
202
203     if( !p_reader->p_sys->p_curtag )
204     {
205         p_reader->p_sys->p_curtag = p_reader->p_sys->p_root;
206         return 1;
207     }
208
209     while( true )
210     {
211         if( (p_child = xtag_next_child( p_reader->p_sys->p_curtag, 0 )) )
212         {
213             p_reader->p_sys->p_curtag = p_child;
214             p_reader->p_sys->p_curattr = NULL;
215             p_reader->p_sys->b_endtag = false;
216             return 1;
217         }
218
219         if( p_reader->p_sys->p_curtag->name && /* no end tag for pcdata */
220             !p_reader->p_sys->b_endtag )
221         {
222             p_reader->p_sys->b_endtag = true;
223             return 1;
224         }
225
226         p_reader->p_sys->b_endtag = false;
227         if( !p_reader->p_sys->p_curtag->parent ) return 0;
228         p_reader->p_sys->p_curtag = p_reader->p_sys->p_curtag->parent;
229     }
230
231     return 0;
232 }
233
234 static int ReaderNodeType( xml_reader_t *p_reader )
235 {
236     if( p_reader->p_sys->p_curtag->name && p_reader->p_sys->b_endtag )
237         return XML_READER_ENDELEM;
238     if( p_reader->p_sys->p_curtag->name )
239         return XML_READER_STARTELEM;
240     if( p_reader->p_sys->p_curtag->pcdata )
241         return XML_READER_TEXT;
242     return XML_READER_NONE;
243 }
244
245 static char *ReaderName( xml_reader_t *p_reader )
246 {
247     const char *psz_name;
248
249     if( !p_reader->p_sys->p_curattr )
250     {
251         psz_name = xtag_get_name( p_reader->p_sys->p_curtag );
252 #ifdef XTAG_DEBUG
253         fprintf( stderr, "TAG: %s\n", psz_name );
254 #endif
255     }
256     else
257         psz_name = ((XAttribute *)p_reader->p_sys->p_curattr->data)->name;
258
259     return psz_name ? strdup( psz_name ) : NULL;
260 }
261
262 static char *ReaderValue( xml_reader_t *p_reader )
263 {
264     const char *psz_name;
265     if( p_reader->p_sys->p_curtag->pcdata )
266     {
267 #ifdef XTAG_DEBUG
268         fprintf( stderr, "%s\n", p_reader->p_sys->p_curtag->pcdata );
269 #endif
270         return strdup( p_reader->p_sys->p_curtag->pcdata );
271     }
272
273     if( !p_reader->p_sys->p_curattr ) return NULL;
274
275 #ifdef XTAG_DEBUG
276     fprintf( stderr, "%s=%s\n", ((XAttribute *)p_reader->p_sys->p_curattr->data)->name,
277             ((XAttribute *)p_reader->p_sys->p_curattr->data)->value );
278 #endif
279
280     psz_name = ((XAttribute *)p_reader->p_sys->p_curattr->data)->value;
281
282     return psz_name ? strdup( psz_name ) : NULL;
283 }
284
285 static int ReaderNextAttr( xml_reader_t *p_reader )
286 {
287     if( !p_reader->p_sys->p_curattr )
288         p_reader->p_sys->p_curattr = p_reader->p_sys->p_curtag->attributes;
289     else if( p_reader->p_sys->p_curattr )
290         p_reader->p_sys->p_curattr = p_reader->p_sys->p_curattr->next;
291
292     return p_reader->p_sys->p_curattr ? VLC_SUCCESS : VLC_EGENERIC;
293 }
294
295 /*****************************************************************************
296  * XTAG parser functions
297  *****************************************************************************/
298
299 static XList *xlist_append( XList *list, void *data )
300 {
301     XList *l, *last;
302
303     l = (XList *)xmalloc( sizeof(XList) );
304     l->prev = l->next = NULL;
305     l->data = data;
306
307     if( !list )
308         return l;
309
310     /* Find the last element */
311     last = list;
312     while( last->next )
313         last = last->next;
314
315     last->next = l;
316     l->prev = last;
317     return list;
318 }
319
320 static void xlist_free( XList *list )
321 {
322     XList *l, *ln;
323
324     for( l = list; l; l = ln )
325     {
326         ln = l->next;
327         free( l );
328     }
329 }
330
331 /* Character classes */
332 #define X_NONE           0
333 #define X_WHITESPACE  1<<0
334 #define X_OPENTAG     1<<1
335 #define X_CLOSETAG    1<<2
336 #define X_DQUOTE      1<<3
337 #define X_SQUOTE      1<<4
338 #define X_EQUAL       1<<5
339 #define X_SLASH       1<<6
340 #define X_QMARK       1<<7
341 #define X_DASH        1<<8
342 #define X_EMARK       1<<9
343
344 static int xtag_cin( char c, int char_class )
345 {
346     if( char_class & X_WHITESPACE ) if( isspace(c) ) return true;
347     if( char_class & X_OPENTAG )    if( c == '<' ) return true;
348     if( char_class & X_CLOSETAG )   if( c == '>' ) return true;
349     if( char_class & X_DQUOTE )     if( c == '"' ) return true;
350     if( char_class & X_SQUOTE )     if( c == '\'' ) return true;
351     if( char_class & X_EQUAL )      if( c == '=' ) return true;
352     if( char_class & X_SLASH )      if( c == '/' ) return true;
353     if( char_class & X_QMARK )      if( c == '?' ) return true;
354     if( char_class & X_DASH  )      if( c == '-' ) return true;
355     if( char_class & X_EMARK )      if( c == '!' ) return true;
356
357     return false;
358 }
359
360 static int xtag_index( XTagParser *parser, int char_class )
361 {
362     char *s = parser->start;
363     int i;
364
365     for( i = 0; s[i] && s != parser->end; i++ )
366     {
367         if( xtag_cin( s[i], char_class ) ) return i;
368     }
369
370     return -1;
371 }
372
373 static void xtag_skip_over( XTagParser *parser, int char_class )
374 {
375     char *s = parser->start;
376     int i;
377
378     if( !parser->valid ) return;
379
380     for( i = 0; s[i] && s != parser->end; i++ )
381     {
382         if( !xtag_cin( s[i], char_class ) )
383         {
384             parser->start = &s[i];
385             return;
386         }
387     }
388
389     return;
390 }
391
392 static void xtag_skip_whitespace( XTagParser * parser )
393 {
394     xtag_skip_over( parser, X_WHITESPACE );
395 }
396
397 static char *xtag_slurp_to( XTagParser *parser, int good_end, int bad_end )
398 {
399     char *ret, *s = parser->start;
400     int xi;
401
402     if( !parser->valid ) return NULL;
403
404     xi = xtag_index( parser, good_end | bad_end );
405
406     if( xi > 0 && xtag_cin (s[xi], good_end) )
407     {
408         ret = xmalloc( xi+1 );
409         strncpy( ret, s, xi );
410         ret[xi] = '\0';
411         parser->start = &s[xi];
412         return ret;
413     }
414
415     return NULL;
416 }
417
418 static int xtag_assert_and_pass( XTagParser *parser, int char_class )
419 {
420     char *s = parser->start;
421
422     if( !parser->valid ) return false;
423
424     if( !xtag_cin( s[0], char_class ) )
425     {
426         parser->valid = false;
427         return false;
428     }
429
430     parser->start = &s[1];
431
432     return true;
433 }
434
435 static char *xtag_slurp_quoted( XTagParser *parser )
436 {
437     char * ret, *s;
438     int quote = X_DQUOTE; /* quote char to match on */
439     int xi;
440
441     if( !parser->valid ) return NULL;
442
443     xtag_skip_whitespace( parser );
444
445     s = parser->start;
446
447     if( xtag_cin( s[0], X_SQUOTE ) ) quote = X_SQUOTE;
448
449     if( !xtag_assert_and_pass( parser, quote ) ) return NULL;
450
451     s = parser->start;
452
453     for( xi = 0; s[xi]; xi++ )
454     {
455         if( xtag_cin( s[xi], quote ) )
456         {
457             if( !(xi > 1 && s[xi-1] == '\\') ) break;
458         }
459     }
460
461     ret = xmalloc( xi+1 );
462     strncpy( ret, s, xi );
463     ret[xi] = '\0';
464     parser->start = &s[xi];
465
466     if( !xtag_assert_and_pass( parser, quote ) )
467     {
468         free( ret );
469         return NULL;
470     }
471
472     return ret;
473 }
474
475 static XAttribute *xtag_parse_attribute( XTagParser *parser )
476 {
477     XAttribute *attr;
478     char *name, *value;
479     char *s;
480
481     if( !parser->valid )
482         return NULL;
483
484     xtag_skip_whitespace( parser );
485  
486     name = xtag_slurp_to( parser, X_WHITESPACE|X_EQUAL, X_SLASH|X_CLOSETAG );
487     if( !name )
488         return NULL;
489
490     xtag_skip_whitespace( parser );
491     s = parser->start;
492
493     if( !xtag_assert_and_pass( parser, X_EQUAL ) )
494     {
495 #ifdef XTAG_DEBUG
496         fprintf( stderr, "xtag: attr failed EQUAL on <%s>\n", name );
497 #endif
498         goto err_free_name;
499     }
500
501     xtag_skip_whitespace( parser );
502
503     value = xtag_slurp_quoted( parser );
504
505     if( value == NULL )
506     {
507 #ifdef XTAG_DEBUG
508         fprintf (stderr, "Got NULL quoted attribute value\n");
509 #endif
510         goto err_free_name;
511     }
512
513     attr = xmalloc( sizeof (*attr) );
514     attr->name = name;
515     attr->value = value;
516     return attr;
517
518  err_free_name:
519     free (name);
520     parser->valid = false;
521     return NULL;
522 }
523
524 static XTag *xtag_parse_tag( XTagParser *parser )
525 {
526     XTag *tag, *inner;
527     XAttribute *attr;
528     char *name;
529     char *pcdata;
530     char *s;
531      int xi;
532
533     if( !parser->valid ) return NULL;
534
535     s = parser->start;
536
537     /* if this starts a comment tag, skip until end */
538     if( (parser->end - parser->start) > 7 &&
539           xtag_cin( s[0], X_OPENTAG ) && xtag_cin( s[1], X_EMARK ) &&
540         xtag_cin( s[2], X_DASH ) && xtag_cin( s[3], X_DASH ) )
541     {
542         parser->start = s = &s[4];
543         while( (xi = xtag_index( parser, X_DASH )) >= 0 )
544         {
545             parser->start = s = &s[xi+1];
546             if( xtag_cin( s[0], X_DASH ) && xtag_cin( s[1], X_CLOSETAG ) )
547             {
548                 parser->start = &s[2];
549                 xtag_skip_whitespace( parser );
550                 return xtag_parse_tag( parser );
551             }
552         }
553         return NULL;
554     }
555
556     /* ignore processing instructions '<?' ... '?>' */
557     if( (parser->end - parser->start) > 4 &&
558           xtag_cin( s[0], X_OPENTAG ) && xtag_cin( s[1], X_QMARK ) )
559     {
560         parser->start = s = &s[2];
561         while ((xi = xtag_index( parser, X_QMARK )) >= 0) {
562             if (xtag_cin( s[xi+1], X_CLOSETAG )) {
563                 parser->start = &s[xi+2];
564                 xtag_skip_whitespace( parser );
565                 return xtag_parse_tag( parser );
566             }
567         }
568         return NULL;
569     }
570
571     /* ignore doctype  '<!DOCTYPE' ... '>' */
572     if ( (parser->end - parser->start) > 8 &&
573             !strncmp( s, "<!DOCTYPE", 9 ) ) {
574         xi = xtag_index( parser, X_CLOSETAG );
575         if ( xi > 0 ) {
576             parser->start = &s[xi+1];
577             xtag_skip_whitespace( parser );
578             return xtag_parse_tag( parser );
579         }
580         else {
581             return NULL;
582         }
583     }
584
585     if( (pcdata = xtag_slurp_to( parser, X_OPENTAG, X_NONE )) != NULL )
586     {
587         tag = xmalloc( sizeof(*tag) );
588         tag->name = NULL;
589         tag->pcdata = pcdata;
590         tag->parent = parser->current_tag;
591         tag->attributes = NULL;
592         tag->children = NULL;
593         tag->current_child = NULL;
594
595         return tag;
596     }
597
598     /* if this starts a close tag, return NULL and let the parent take it */
599     if( xtag_cin( s[0], X_OPENTAG ) && xtag_cin( s[1], X_SLASH ) )
600         return NULL;
601
602     /* parse CDATA content */
603     if ( (parser->end - parser->start) > 8 &&
604             !strncmp( s, "<![CDATA[", 9 ) ) {
605         parser->start = s = &s[9];
606         while (parser->end - s > 2) {
607             if (strncmp( s, "]]>", 3 ) == 0) {
608                 if ( !(tag = malloc( sizeof(*tag))) ) return NULL;
609                 if ( !(pcdata = malloc( s - parser->start + 1)) )
610                 {
611                     free( tag );
612                     return NULL;
613                 }
614                 strncpy( pcdata, parser->start, s - parser->start );
615                 pcdata[s - parser->start]='\0';
616                 parser->start = &s[3];
617                 tag->name = NULL;
618                 tag->pcdata = pcdata;
619                 tag->parent = parser->current_tag;
620                 tag->attributes = NULL;
621                 tag->children = NULL;
622                 tag->current_child = NULL;
623                 return tag;
624             }
625             else {
626                 s++;
627             }
628         }
629         return NULL;
630     }
631
632     if( !xtag_assert_and_pass( parser, X_OPENTAG ) ) return NULL;
633
634     name = xtag_slurp_to( parser, X_WHITESPACE|X_SLASH|X_CLOSETAG, X_NONE );
635     if( name == NULL ) return NULL;
636
637 #ifdef XTAG_DEBUG
638     fprintf (stderr, "<%s ...\n", name);
639 #endif
640
641     tag = xmalloc( sizeof(*tag) );
642     tag->name = name;
643     tag->pcdata = NULL;
644     tag->parent = parser->current_tag;
645     tag->attributes = NULL;
646     tag->children = NULL;
647     tag->current_child = NULL;
648
649     s = parser->start;
650
651     if( xtag_cin( s[0], X_WHITESPACE ) )
652     {
653         while( (attr = xtag_parse_attribute( parser )) != NULL )
654         {
655             tag->attributes = xlist_append( tag->attributes, attr );
656         }
657     }
658
659     xtag_skip_whitespace( parser );
660
661     s = parser->start;
662
663     if( xtag_cin( s[0], X_CLOSETAG ) )
664     {
665         parser->current_tag = tag;
666
667         xtag_assert_and_pass( parser, X_CLOSETAG );
668
669         while( (inner = xtag_parse_tag( parser ) ) != NULL )
670         {
671             tag->children = xlist_append( tag->children, inner );
672         }
673
674         parser->current_tag = tag->parent;
675         xtag_skip_whitespace( parser );
676
677         xtag_assert_and_pass( parser, X_OPENTAG );
678         xtag_assert_and_pass( parser, X_SLASH );
679         name = xtag_slurp_to( parser, X_WHITESPACE | X_CLOSETAG, X_NONE );
680         if( name )
681         {
682             if( strcmp( name, tag->name ) )
683             {
684 #ifdef XTAG_DEBUG
685                 fprintf (stderr, "got %s expected %s\n", name, tag->name);
686 #endif
687                 parser->valid = false;
688             }
689             free( name );
690         }
691
692         xtag_skip_whitespace( parser );
693         xtag_assert_and_pass( parser, X_CLOSETAG );
694         xtag_skip_whitespace( parser );
695     }
696     else
697     {
698         xtag_assert_and_pass( parser, X_SLASH );
699         xtag_assert_and_pass( parser, X_CLOSETAG );
700         xtag_skip_whitespace( parser );
701     }
702
703     return tag;
704 }
705
706 static void xtag_free( XTag *xtag )
707 {
708     XList *l;
709     XAttribute *attr;
710     XTag *child;
711
712     if( !xtag )
713         return;
714
715     free( xtag->name );
716     free( xtag->pcdata );
717
718     for( l = xtag->attributes; l; l = l->next )
719     {
720         if( (attr = (XAttribute *)l->data) != NULL )
721         {
722             free( attr->name );
723             free( attr->value );
724             free( attr );
725         }
726     }
727     xlist_free( xtag->attributes );
728
729     for( l = xtag->children; l; l = l->next )
730     {
731         child = (XTag *)l->data;
732         xtag_free( child );
733     }
734     xlist_free( xtag->children );
735
736     free( xtag );
737 }
738
739 static XTag *xtag_new_parse( const char *s, int n )
740 {
741     XTagParser parser;
742     XTag *tag, *ttag, *wrapper;
743
744     parser.valid = true;
745     parser.current_tag = NULL;
746     parser.start = (char *)s;
747
748     if( n == -1 ) parser.end = NULL;
749     else if( n == 0 )
750     {
751 #ifdef XTAG_DEBUG
752         fprintf (stderr, "empty buffer\n");
753 #endif
754         return NULL;
755     }
756     else parser.end = (char *)&s[n];
757
758     /* can't have whitespace pcdata outside rootnode */
759     xtag_skip_whitespace( &parser );
760
761     tag = xtag_parse_tag( &parser );
762
763     if( !parser.valid )
764     {
765 #ifdef XTAG_DEBUG
766         fprintf (stderr, "invalid file\n");
767 #endif
768         xtag_free( tag );
769         return NULL;
770     }
771
772     if( (ttag = xtag_parse_tag( &parser )) != NULL )
773     {
774         if( !parser.valid )
775         {
776             xtag_free( ttag );
777             return tag;
778         }
779
780         wrapper = xmalloc( sizeof(XTag) );
781         wrapper->name = NULL;
782         wrapper->pcdata = NULL;
783         wrapper->parent = NULL;
784         wrapper->attributes = NULL;
785         wrapper->children = NULL;
786         wrapper->current_child = NULL;
787
788         wrapper->children = xlist_append( wrapper->children, tag );
789         wrapper->children = xlist_append( wrapper->children, ttag );
790
791         while( (ttag = xtag_parse_tag( &parser )) != NULL )
792         {
793             if( !parser.valid )
794             {
795                 xtag_free( ttag );
796                 return wrapper;
797             }
798
799             wrapper->children = xlist_append( wrapper->children, ttag );
800         }
801         return wrapper;
802     }
803
804     return tag;
805 }
806
807 static char *xtag_get_name( XTag *xtag )
808 {
809     return xtag ? xtag->name : NULL;
810 }
811
812 #if 0
813 static char *xtag_get_pcdata( XTag *xtag )
814 {
815     XList *l;
816     XTag *child;
817
818     if( xtag == NULL ) return NULL;
819
820     for( l = xtag->children; l; l = l->next )
821     {
822         child = (XTag *)l->data;
823         if( child->pcdata != NULL )
824         {
825             return child->pcdata;
826         }
827     }
828
829     return NULL;
830 }
831
832 static char *xtag_get_attribute( XTag *xtag, char *attribute )
833 {
834     XList *l;
835     XAttribute *attr;
836
837     if( xtag == NULL ) return NULL;
838
839     for( l = xtag->attributes; l; l = l->next )
840     {
841         if( (attr = (XAttribute *)l->data) != NULL )
842         {
843             if( !strcmp( attr->name, attribute ) ) return attr->value;
844         }
845     }
846
847     return NULL;
848 }
849 #endif
850
851 static XTag *xtag_first_child( XTag *xtag, char *name )
852 {
853     XList *l;
854     XTag *child;
855
856     if( xtag == NULL ) return NULL;
857     if( (l = xtag->children) == NULL ) return NULL;
858
859     if( name == NULL )
860     {
861         xtag->current_child = l;
862         return (XTag *)l->data;
863     }
864
865     for( ; l; l = l->next )
866     {
867         child = (XTag *)l->data;
868
869         if( !strcmp( child->name, name ) )
870         {
871             xtag->current_child = l;
872             return child;
873         }
874     }
875
876     xtag->current_child = NULL;
877
878     return NULL;
879 }
880
881 static XTag *xtag_next_child( XTag *xtag, char *name )
882 {
883     XList *l;
884     XTag *child;
885
886     if( xtag == NULL ) return NULL;
887
888     if( (l = xtag->current_child) == NULL )
889         return xtag_first_child( xtag, name );
890
891     if( (l = l->next) == NULL ) return NULL;
892
893     if( name == NULL )
894     {
895         xtag->current_child = l;
896         return (XTag *)l->data;
897     }
898
899     for( ; l; l = l->next )
900     {
901         child = (XTag *)l->data;
902
903         if( !strcmp( child->name, name ) )
904         {
905             xtag->current_child = l;
906             return child;
907         }
908     }
909
910     xtag->current_child = NULL;
911
912     return NULL;
913 }
914
915 #if 0
916 /*
917  * This snprints function takes a variable list of char *, the last of
918  * which must be NULL, and prints each in turn to buf.
919  * Returns C99-style total length that would have been written, even if
920  * this is larger than n.
921  */
922 static int xtag_snprints( char *buf, int n, ... )
923 {
924     va_list ap;
925     char *s;
926     int len, to_copy, total = 0;
927
928     va_start( ap, n );
929  
930     for( s = va_arg( ap, char * ); s; s = va_arg( ap, char *) )
931     {
932         len = strlen (s);
933
934         if( (to_copy = __MIN(n, len) ) > 0 )
935         {
936             memcpy( buf, s, to_copy );
937             buf += to_copy;
938             n -= to_copy;
939         }
940
941         total += len;
942     }
943
944     va_end( ap );
945
946     return total;
947 }
948
949 static int xtag_snprint( char *buf, int n, XTag *xtag )
950 {
951     int nn, written = 0;
952     XList *l;
953     XAttribute *attr;
954     XTag *child;
955
956 #define FORWARD(N) \
957     buf += __MIN(n, N); \
958     n = __MAX(n-N, 0);  \
959     written += N;
960
961     if( xtag == NULL )
962     {
963         if( n > 0 ) buf[0] = '\0';
964         return 0;
965     }
966
967     if( xtag->pcdata )
968     {
969         nn = xtag_snprints( buf, n, xtag->pcdata, NULL );
970         FORWARD( nn );
971
972         return written;
973     }
974
975     if( xtag->name )
976     {
977         nn = xtag_snprints( buf, n, "<", xtag->name, NULL );
978         FORWARD( nn );
979
980         for( l = xtag->attributes; l; l = l->next )
981         {
982             attr = (XAttribute *)l->data;
983  
984             nn = xtag_snprints( buf, n, " ", attr->name, "=\"", attr->value,
985                                 "\"", NULL);
986             FORWARD( nn );
987         }
988
989         if( xtag->children == NULL )
990         {
991             nn = xtag_snprints ( buf, n, "/>", NULL );
992             FORWARD( nn );
993
994             return written;
995         }
996
997         nn = xtag_snprints( buf, n, ">", NULL );
998         FORWARD( nn );
999     }
1000
1001     for( l = xtag->children; l; l = l->next )
1002     {
1003         child = (XTag *)l->data;
1004
1005         nn = xtag_snprint( buf, n, child );
1006         FORWARD( nn );
1007     }
1008
1009     if( xtag->name )
1010     {
1011         nn = xtag_snprints( buf, n, "</", xtag->name, ">", NULL );
1012         FORWARD( nn );
1013     }
1014
1015     return written;
1016 }
1017 #endif
1018