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