]> git.sesse.net Git - vlc/blob - modules/misc/xml/xtag.c
ef33b2a05156c7098791e54cd14cd5c658eecda6
[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/vlc.h>
34 #include <vlc_plugin.h>
35
36 #include "vlc_xml.h"
37 #include "vlc_block.h"
38 #include "vlc_stream.h"
39
40 #include <ctype.h>
41 #include <stdarg.h>
42
43 #include <assert.h>
44
45 #undef XTAG_DEBUG
46
47 typedef struct _XList
48 {
49     struct _XList *prev;
50     struct _XList *next;
51     void *data;
52 } XList;
53
54 /*
55  * struct XTag is kind of a union ... it normally represents a whole
56  * tag (and its children), but it could alternatively represent some
57  * PCDATA. Basically, if tag->pcdata is non-NULL, interpret only it and
58  * ignore the name, attributes and inner_tags.
59  */
60 typedef struct _XTag
61 {
62     char *name;
63     char *pcdata;
64     struct _XTag *parent;
65     XList *attributes;
66     XList *children;
67     XList *current_child;
68 } XTag;
69
70 typedef struct _XAttribute
71 {
72     char *name;
73     char *value;
74 } XAttribute;
75
76 typedef struct _XTagParser
77 {
78     int valid; /* boolean */
79     XTag *current_tag;
80     char *start;
81     char *end;
82 } XTagParser;
83
84 /*****************************************************************************
85  * Module descriptor
86  *****************************************************************************/
87 static int  Open ( vlc_object_t * );
88 static void Close( vlc_object_t * );
89
90 vlc_module_begin();
91     set_description( N_("Simple XML Parser") );
92     set_capability( "xml", 5 );
93     set_callbacks( Open, Close );
94 vlc_module_end();
95
96 struct xml_reader_sys_t
97 {
98     XTag *p_root; /* Root tag */
99     XTag *p_curtag; /* Current tag */
100     XList *p_curattr; /* Current attribute */
101     bool b_endtag;
102 };
103
104 static xml_reader_t *ReaderCreate( xml_t *, stream_t * );
105 static void ReaderDelete( xml_reader_t * );
106 static int ReaderRead( xml_reader_t * );
107 static int ReaderNodeType( xml_reader_t * );
108 static char *ReaderName( xml_reader_t * );
109 static char *ReaderValue( xml_reader_t * );
110 static int ReaderNextAttr( xml_reader_t * );
111
112 static int ReaderUseDTD ( xml_reader_t *, bool );
113
114 static void CatalogLoad( xml_t *, const char * );
115 static void CatalogAdd( xml_t *, const char *, const char *, const char * );
116
117 static XTag *xtag_new_parse( const char *, int );
118 static char *xtag_get_name( XTag * );
119 #if 0
120 static char *xtag_get_pcdata( XTag * );
121 static char *xtag_get_attribute( XTag *, char * );
122 #endif
123 static XTag *xtag_first_child( XTag *, char * );
124 static XTag *xtag_next_child( XTag *, char * );
125 static XTag *xtag_free( XTag * );
126 static int xtag_snprint( char *, int, XTag * );
127
128 /*****************************************************************************
129  * Module initialization
130  *****************************************************************************/
131 static int Open( vlc_object_t *p_this )
132 {
133     xml_t *p_xml = (xml_t *)p_this;
134
135     p_xml->pf_reader_create = ReaderCreate;
136     p_xml->pf_reader_delete = ReaderDelete;
137
138     p_xml->pf_catalog_load = CatalogLoad;
139     p_xml->pf_catalog_add  = CatalogAdd;
140
141     return VLC_SUCCESS;
142 }
143
144 /*****************************************************************************
145  * Module deinitialization
146  *****************************************************************************/
147 static void Close( vlc_object_t *p_this )
148 {
149     VLC_UNUSED(p_this);
150     return;
151 }
152
153 /*****************************************************************************
154  * Catalogue functions
155  *****************************************************************************/
156 static void CatalogLoad( xml_t *p_xml, const char *psz_filename )
157 {
158     VLC_UNUSED(psz_filename);
159     msg_Dbg( p_xml, "catalog support not implemented" );
160 }
161
162 static void CatalogAdd( xml_t *p_xml, const char *psz_arg1,
163                           const char *psz_arg2, const char *psz_filename )
164 {
165     VLC_UNUSED(p_xml); VLC_UNUSED(psz_arg1); VLC_UNUSED(psz_arg2);
166     VLC_UNUSED(psz_filename);
167 }
168
169 /*****************************************************************************
170  * Reader functions
171  *****************************************************************************/
172 static xml_reader_t *ReaderCreate( xml_t *p_xml, stream_t *s )
173 {
174     xml_reader_t *p_reader;
175     char *p_buffer, *p_new;
176     int i_size, i_pos = 0, i_buffer = 2048;
177     XTag *p_root;
178
179     /* Open and read file */
180     p_buffer = malloc( i_buffer );
181     if( p_buffer == NULL ) {
182         msg_Err( p_xml, "out of memory" );
183         return NULL;
184     }
185
186     while( ( i_size = stream_Read( s, &p_buffer[i_pos], 2048 ) ) == 2048 )
187     {
188         i_pos += i_size;
189         i_buffer += i_size;
190         p_new = realloc( p_buffer, i_buffer );
191         if( !p_new )
192         {
193             msg_Err( p_xml, "out of memory" );
194             free( p_buffer );
195             return NULL;
196         }
197         p_buffer = p_new;
198     }
199     p_buffer[ i_pos + i_size ] = 0; /* 0 terminated string */
200
201     if( i_pos + i_size == 0 )
202     {
203         msg_Dbg( p_xml, "empty XML" );
204         free( p_buffer );
205         return 0;
206     }
207
208     p_root = xtag_new_parse( p_buffer, i_buffer );
209     if( !p_root )
210     {
211         msg_Warn( p_xml, "couldn't parse XML" );
212         free( p_buffer );
213         return 0;
214     }
215
216     free( p_buffer );
217     p_reader = malloc( sizeof(xml_reader_t) );
218     p_reader->p_sys = malloc( sizeof(xml_reader_sys_t) );
219     p_reader->p_sys->p_root = p_root;
220     p_reader->p_sys->p_curtag = NULL;
221     p_reader->p_sys->p_curattr = NULL;
222     p_reader->p_sys->b_endtag = false;
223     p_reader->p_xml = p_xml;
224
225     p_reader->pf_read = ReaderRead;
226     p_reader->pf_node_type = ReaderNodeType;
227     p_reader->pf_name = ReaderName;
228     p_reader->pf_value = ReaderValue;
229     p_reader->pf_next_attr = ReaderNextAttr;
230     p_reader->pf_use_dtd = ReaderUseDTD;
231
232     return p_reader;
233 }
234
235 static void ReaderDelete( xml_reader_t *p_reader )
236 {
237     xtag_free( p_reader->p_sys->p_root );
238     free( p_reader->p_sys );
239     free( p_reader );
240 }
241
242 static int ReaderUseDTD ( xml_reader_t *p_reader, bool b_use )
243 {
244     VLC_UNUSED(p_reader); VLC_UNUSED(b_use);
245     return VLC_EGENERIC;
246 }
247
248 static int ReaderRead( xml_reader_t *p_reader )
249 {
250     XTag *p_child;
251
252     if( !p_reader->p_sys->p_curtag )
253     {
254         p_reader->p_sys->p_curtag = p_reader->p_sys->p_root;
255         return 1;
256     }
257
258     while( 1 )
259     {
260         if( (p_child = xtag_next_child( p_reader->p_sys->p_curtag, 0 )) )
261         {
262             p_reader->p_sys->p_curtag = p_child;
263             p_reader->p_sys->p_curattr = 0;
264             p_reader->p_sys->b_endtag = false;
265             return 1;
266         }
267
268         if( p_reader->p_sys->p_curtag->name && /* no end tag for pcdata */
269             !p_reader->p_sys->b_endtag )
270         {
271             p_reader->p_sys->b_endtag = true;
272             return 1;
273         }
274
275         p_reader->p_sys->b_endtag = false;
276         if( !p_reader->p_sys->p_curtag->parent ) return 0;
277         p_reader->p_sys->p_curtag = p_reader->p_sys->p_curtag->parent;
278     }
279
280     return 0;
281 }
282
283 static int ReaderNodeType( xml_reader_t *p_reader )
284 {
285     if( p_reader->p_sys->p_curtag->name &&
286         p_reader->p_sys->b_endtag ) return XML_READER_ENDELEM;
287     if( p_reader->p_sys->p_curtag->name ) return XML_READER_STARTELEM;
288     if( p_reader->p_sys->p_curtag->pcdata ) 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         printf( "TAG: %s\n", psz_name );
301 #endif
302     }
303     else
304         psz_name = ((XAttribute *)p_reader->p_sys->p_curattr->data)->name;
305
306     if( psz_name ) return strdup( psz_name );
307     else return 0;
308 }
309
310 static char *ReaderValue( xml_reader_t *p_reader )
311 {
312     const char *psz_name;
313     if( p_reader->p_sys->p_curtag->pcdata )
314     {
315 #ifdef XTAG_DEBUG
316         printf( "%s\n", p_reader->p_sys->p_curtag->pcdata );
317 #endif
318         return strdup( p_reader->p_sys->p_curtag->pcdata );
319     }
320
321     if( !p_reader->p_sys->p_curattr ) return 0;
322
323 #ifdef XTAG_DEBUG
324     printf( "%s=%s\n", ((XAttribute *)p_reader->p_sys->p_curattr->data)->name,
325             ((XAttribute *)p_reader->p_sys->p_curattr->data)->value );
326 #endif
327
328     psz_name = ((XAttribute *)p_reader->p_sys->p_curattr->data)->value;
329
330     if( psz_name ) return strdup( psz_name );
331     else return 0;
332 }
333
334 static int ReaderNextAttr( xml_reader_t *p_reader )
335 {
336     if( !p_reader->p_sys->p_curattr )
337         p_reader->p_sys->p_curattr = p_reader->p_sys->p_curtag->attributes;
338     else if( p_reader->p_sys->p_curattr )
339         p_reader->p_sys->p_curattr = p_reader->p_sys->p_curattr->next;
340  
341     if( p_reader->p_sys->p_curattr ) return VLC_SUCCESS;
342     else return VLC_EGENERIC;
343 }
344
345 /*****************************************************************************
346  * XTAG parser functions
347  *****************************************************************************/
348
349 static XList *xlist_append( XList *list, void *data )
350 {
351     XList *l, *last;
352
353     l = (XList *)malloc( sizeof(XList) );
354     l->prev = l->next = NULL;
355     l->data = data;
356
357     if( list == NULL ) return l;
358
359     for( last = list; last; last = last->next )
360         if( last->next == NULL ) break;
361
362     if( last ) 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 = malloc( (xi+1) * sizeof(char) );
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 = malloc( (xi+1) * sizeof(char) );
509     strncpy( ret, s, xi );
510     ret[xi] = '\0';
511     parser->start = &s[xi];
512
513     if( !xtag_assert_and_pass( parser, quote ) ) return NULL;
514
515     return ret;
516 }
517
518 static XAttribute *xtag_parse_attribute( XTagParser *parser )
519 {
520     XAttribute *attr;
521     char *name, *value;
522     char *s;
523
524     if( !parser->valid ) return NULL;
525
526     xtag_skip_whitespace( parser );
527  
528     name = xtag_slurp_to( parser, X_WHITESPACE|X_EQUAL, X_SLASH|X_CLOSETAG );
529     if( name == NULL ) return NULL;
530
531     xtag_skip_whitespace( parser );
532     s = parser->start;
533
534     if( !xtag_assert_and_pass( parser, X_EQUAL ) )
535     {
536 #ifdef XTAG_DEBUG
537         printf( "xtag: attr failed EQUAL on <%s>\n", name );
538 #endif
539         goto err_free_name;
540     }
541
542     xtag_skip_whitespace( parser );
543
544     value = xtag_slurp_quoted( parser );
545
546     if( value == NULL )
547     {
548 #ifdef XTAG_DEBUG
549         printf ("Got NULL quoted attribute value\n");
550 #endif
551         goto err_free_name;
552     }
553
554     attr = malloc( sizeof (*attr) );
555     attr->name = name;
556     attr->value = value;
557     return attr;
558
559  err_free_name:
560     free (name);
561     parser->valid = false;
562     return NULL;
563 }
564
565 static XTag *xtag_parse_tag( XTagParser *parser )
566 {
567     XTag *tag, *inner;
568     XAttribute *attr;
569     char *name;
570     char *pcdata;
571     char *s;
572      int xi;
573
574     if( !parser->valid ) return NULL;
575
576     s = parser->start;
577
578     /* if this starts a comment tag, skip until end */
579     if( (parser->end - parser->start) > 7 &&
580           xtag_cin( s[0], X_OPENTAG ) && xtag_cin( s[1], X_EMARK ) &&
581         xtag_cin( s[2], X_DASH ) && xtag_cin( s[3], X_DASH ) )
582     {
583         parser->start = s = &s[4];
584         while( (xi = xtag_index( parser, X_DASH )) >= 0 )
585         {
586             parser->start = s = &s[xi+1];
587             if( xtag_cin( s[0], X_DASH ) && xtag_cin( s[1], X_CLOSETAG ) )
588             {
589                 parser->start = &s[2];
590                 xtag_skip_whitespace( parser );
591                 return xtag_parse_tag( parser );
592             }
593         }
594         return NULL;
595     }
596
597     /* ignore processing instructions '<?' ... '?>' */
598     if( (parser->end - parser->start) > 4 &&
599           xtag_cin( s[0], X_OPENTAG ) && xtag_cin( s[1], X_QMARK ) )
600     {
601         parser->start = s = &s[2];
602         while ((xi = xtag_index( parser, X_QMARK )) >= 0) {
603             if (xtag_cin( s[xi+1], X_CLOSETAG )) {
604                 parser->start = &s[xi+2];
605                 xtag_skip_whitespace( parser );
606                 return xtag_parse_tag( parser );
607             }
608         }
609         return NULL;
610     }
611
612     /* ignore doctype  '<!DOCTYPE' ... '>' */
613     if ( (parser->end - parser->start) > 8 &&
614             !strncmp( s, "<!DOCTYPE", 9 ) ) {
615         xi = xtag_index( parser, X_CLOSETAG );
616         if ( xi > 0 ) {
617             parser->start = s = &s[xi+1];
618             xtag_skip_whitespace( parser );
619             return xtag_parse_tag( parser );
620         }
621         else {
622             return NULL;
623         }
624     }
625
626     if( (pcdata = xtag_slurp_to( parser, X_OPENTAG, X_NONE )) != NULL )
627     {
628         tag = malloc( sizeof(*tag) );
629         tag->name = NULL;
630         tag->pcdata = pcdata;
631         tag->parent = parser->current_tag;
632         tag->attributes = NULL;
633         tag->children = NULL;
634         tag->current_child = NULL;
635
636         return tag;
637     }
638
639     /* if this starts a close tag, return NULL and let the parent take it */
640     if( xtag_cin( s[0], X_OPENTAG ) && xtag_cin( s[1], X_SLASH ) )
641         return NULL;
642
643     /* parse CDATA content */
644     if ( (parser->end - parser->start) > 8 &&
645             !strncmp( s, "<![CDATA[", 9 ) ) {
646         parser->start = s = &s[9];
647         while (parser->end - s > 2) {
648             if (strncmp( s, "]]>", 3 ) == 0) {
649                 if ( !(tag = malloc( sizeof(*tag))) ) return NULL;
650                 if ( !(pcdata = malloc( sizeof(char)*(s - parser->start + 1))) ) return NULL;
651                 strncpy( pcdata, parser->start, s - parser->start );
652                 pcdata[s - parser->start]='\0';
653                 parser->start = s = &s[3];
654                 tag->name = NULL;
655                 tag->pcdata = pcdata;
656                 tag->parent = parser->current_tag;
657                 tag->attributes = NULL;
658                 tag->children = NULL;
659                 tag->current_child = NULL;
660                 return tag;
661             }
662             else {
663                 s++;
664             }
665         }
666         return NULL;
667     }
668
669     if( !xtag_assert_and_pass( parser, X_OPENTAG ) ) return NULL;
670
671     name = xtag_slurp_to( parser, X_WHITESPACE|X_SLASH|X_CLOSETAG, X_NONE );
672     if( name == NULL ) return NULL;
673
674 #ifdef XTAG_DEBUG
675     printf ("<%s ...\n", name);
676 #endif
677
678     tag = malloc( sizeof(*tag) );
679     tag->name = name;
680     tag->pcdata = NULL;
681     tag->parent = parser->current_tag;
682     tag->attributes = NULL;
683     tag->children = NULL;
684     tag->current_child = NULL;
685
686     s = parser->start;
687
688     if( xtag_cin( s[0], X_WHITESPACE ) )
689     {
690         while( (attr = xtag_parse_attribute( parser )) != NULL )
691         {
692             tag->attributes = xlist_append( tag->attributes, attr );
693         }
694     }
695
696     xtag_skip_whitespace( parser );
697
698     s = parser->start;
699
700     if( xtag_cin( s[0], X_CLOSETAG ) )
701     {
702         parser->current_tag = tag;
703
704         xtag_assert_and_pass( parser, X_CLOSETAG );
705
706         while( (inner = xtag_parse_tag( parser ) ) != NULL )
707         {
708             tag->children = xlist_append( tag->children, inner );
709         }
710
711         parser->current_tag = tag->parent;
712         xtag_skip_whitespace( parser );
713
714         xtag_assert_and_pass( parser, X_OPENTAG );
715         xtag_assert_and_pass( parser, X_SLASH );
716         name = xtag_slurp_to( parser, X_WHITESPACE | X_CLOSETAG, X_NONE );
717         if( name )
718         {
719             if( strcmp( name, tag->name ) )
720             {
721 #ifdef XTAG_DEBUG
722                 printf ("got %s expected %s\n", name, tag->name);
723 #endif
724                 parser->valid = false;
725             }
726             free( name );
727         }
728
729         xtag_skip_whitespace( parser );
730         xtag_assert_and_pass( parser, X_CLOSETAG );
731         xtag_skip_whitespace( parser );
732     }
733     else
734     {
735         xtag_assert_and_pass( parser, X_SLASH );
736         xtag_assert_and_pass( parser, X_CLOSETAG );
737         xtag_skip_whitespace( parser );
738     }
739
740     return tag;
741 }
742
743 static XTag *xtag_free( XTag *xtag )
744 {
745     XList *l;
746     XAttribute *attr;
747     XTag *child;
748
749     if( xtag == NULL ) return NULL;
750
751     free( xtag->name );
752     free( xtag->pcdata );
753
754     for( l = xtag->attributes; l; l = l->next )
755     {
756         if( (attr = (XAttribute *)l->data) != NULL )
757         {
758             free( attr->name );
759             free( attr->value );
760             free( attr );
761         }
762     }
763     xlist_free( xtag->attributes );
764
765     for( l = xtag->children; l; l = l->next )
766     {
767         child = (XTag *)l->data;
768         xtag_free( child );
769     }
770     xlist_free( xtag->children );
771
772     free( xtag );
773
774     return NULL;
775 }
776
777 static XTag *xtag_new_parse( const char *s, int n )
778 {
779     XTagParser parser;
780     XTag *tag, *ttag, *wrapper;
781
782     parser.valid = true;
783     parser.current_tag = NULL;
784     parser.start = (char *)s;
785
786     if( n == -1 ) parser.end = NULL;
787     else if( n == 0 )
788     {
789 #ifdef XTAG_DEBUG
790         printf ("empty buffer");
791 #endif
792         return NULL;
793     }
794     else parser.end = (char *)&s[n];
795
796     /* can't have whitespace pcdata outside rootnode */
797     xtag_skip_whitespace( &parser );
798
799     tag = xtag_parse_tag( &parser );
800
801     if( !parser.valid )
802     {
803 #ifdef XTAG_DEBUG
804         printf ("invalid file");
805 #endif
806         xtag_free( tag );
807         return NULL;
808     }
809
810     if( (ttag = xtag_parse_tag( &parser )) != NULL )
811     {
812         if( !parser.valid )
813         {
814             xtag_free( ttag );
815             return tag;
816         }
817
818         wrapper = malloc( sizeof(XTag) );
819         wrapper->name = NULL;
820         wrapper->pcdata = NULL;
821         wrapper->parent = NULL;
822         wrapper->attributes = NULL;
823         wrapper->children = NULL;
824         wrapper->current_child = NULL;
825
826         wrapper->children = xlist_append( wrapper->children, tag );
827         wrapper->children = xlist_append( wrapper->children, ttag );
828
829         while( (ttag = xtag_parse_tag( &parser )) != NULL )
830         {
831             if( !parser.valid )
832             {
833                 xtag_free( ttag );
834                 return wrapper;
835             }
836
837             wrapper->children = xlist_append( wrapper->children, ttag );
838         }
839         return wrapper;
840     }
841
842     return tag;
843 }
844
845 static char *xtag_get_name( XTag *xtag )
846 {
847     return xtag ? xtag->name : NULL;
848 }
849
850 #if 0
851 static char *xtag_get_pcdata( XTag *xtag )
852 {
853     XList *l;
854     XTag *child;
855
856     if( xtag == NULL ) return NULL;
857
858     for( l = xtag->children; l; l = l->next )
859     {
860         child = (XTag *)l->data;
861         if( child->pcdata != NULL )
862         {
863             return child->pcdata;
864         }
865     }
866
867     return NULL;
868 }
869
870 static char *xtag_get_attribute( XTag *xtag, char *attribute )
871 {
872     XList *l;
873     XAttribute *attr;
874
875     if( xtag == NULL ) return NULL;
876
877     for( l = xtag->attributes; l; l = l->next )
878     {
879         if( (attr = (XAttribute *)l->data) != NULL )
880         {
881             if( !strcmp( attr->name, attribute ) ) return attr->value;
882         }
883     }
884
885     return NULL;
886 }
887 #endif
888
889 static XTag *xtag_first_child( XTag *xtag, char *name )
890 {
891     XList *l;
892     XTag *child;
893
894     if( xtag == NULL ) return NULL;
895     if( (l = xtag->children) == NULL ) return NULL;
896
897     if( name == NULL )
898     {
899         xtag->current_child = l;
900         return (XTag *)l->data;
901     }
902
903     for( ; l; l = l->next )
904     {
905         child = (XTag *)l->data;
906
907         if( !strcmp( child->name, name ) )
908         {
909             xtag->current_child = l;
910             return child;
911         }
912     }
913
914     xtag->current_child = NULL;
915
916     return NULL;
917 }
918
919 static XTag *xtag_next_child( XTag *xtag, char *name )
920 {
921     XList *l;
922     XTag *child;
923
924     if( xtag == NULL ) return NULL;
925
926     if( (l = xtag->current_child) == NULL )
927         return xtag_first_child( xtag, name );
928
929     if( (l = l->next) == NULL ) return NULL;
930
931     if( name == NULL )
932     {
933         xtag->current_child = l;
934         return (XTag *)l->data;
935     }
936
937     for( ; l; l = l->next )
938     {
939         child = (XTag *)l->data;
940
941         if( !strcmp( child->name, name ) )
942         {
943             xtag->current_child = l;
944             return child;
945         }
946     }
947
948     xtag->current_child = NULL;
949
950     return NULL;
951 }
952
953 /*
954  * This snprints function takes a variable list of char *, the last of
955  * which must be NULL, and prints each in turn to buf.
956  * Returns C99-style total length that would have been written, even if
957  * this is larger than n.
958  */
959 static int xtag_snprints( char *buf, int n, ... )
960 {
961     va_list ap;
962     char *s;
963     int len, to_copy, total = 0;
964
965     va_start( ap, n );
966  
967     for( s = va_arg( ap, char * ); s; s = va_arg( ap, char *) )
968     {
969         len = strlen (s);
970
971         if( (to_copy = __MIN(n, len) ) > 0 )
972         {
973             memcpy( buf, s, to_copy );
974             buf += to_copy;
975             n -= to_copy;
976         }
977
978         total += len;
979     }
980
981     va_end( ap );
982
983     return total;
984 }
985
986 static int xtag_snprint( char *buf, int n, XTag *xtag )
987 {
988     int nn, written = 0;
989     XList *l;
990     XAttribute *attr;
991     XTag *child;
992
993 #define FORWARD(N) \
994     buf += __MIN(n, N); \
995     n = __MAX(n-N, 0);  \
996     written += N;
997
998     if( xtag == NULL )
999     {
1000         if( n > 0 ) buf[0] = '\0';
1001         return 0;
1002     }
1003
1004     if( xtag->pcdata )
1005     {
1006         nn = xtag_snprints( buf, n, xtag->pcdata, NULL );
1007         FORWARD( nn );
1008
1009         return written;
1010     }
1011
1012     if( xtag->name )
1013     {
1014         nn = xtag_snprints( buf, n, "<", xtag->name, NULL );
1015         FORWARD( nn );
1016
1017         for( l = xtag->attributes; l; l = l->next )
1018         {
1019             attr = (XAttribute *)l->data;
1020  
1021             nn = xtag_snprints( buf, n, " ", attr->name, "=\"", attr->value,
1022                                 "\"", NULL);
1023             FORWARD( nn );
1024         }
1025
1026         if( xtag->children == NULL )
1027         {
1028             nn = xtag_snprints ( buf, n, "/>", NULL );
1029             FORWARD( nn );
1030
1031             return written;
1032         }
1033
1034         nn = xtag_snprints( buf, n, ">", NULL );
1035         FORWARD( nn );
1036     }
1037
1038     for( l = xtag->children; l; l = l->next )
1039     {
1040         child = (XTag *)l->data;
1041
1042         nn = xtag_snprint( buf, n, child );
1043         FORWARD( nn );
1044     }
1045
1046     if( xtag->name )
1047     {
1048         nn = xtag_snprints( buf, n, "</", xtag->name, ">", NULL );
1049         FORWARD( nn );
1050     }
1051
1052     return written;
1053 }