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
10 * Authors: Conrad Parker <Conrad.Parker@csiro.au>
11 * Andre Pang <Andre.Pang@csiro.au>
12 * Gildas Bazin <gbazin@videolan.org>
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.
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.
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 *****************************************************************************/
33 #include <vlc_common.h>
34 #include <vlc_plugin.h>
37 #include <vlc_block.h>
38 #include <vlc_stream.h>
39 #include <vlc_memory.h>
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.
69 typedef struct _XAttribute
75 typedef struct _XTagParser
77 int valid
; /* boolean */
83 /*****************************************************************************
85 *****************************************************************************/
86 static int Open ( vlc_object_t
* );
87 static void Close( vlc_object_t
* );
90 set_description( N_("Simple XML Parser") )
91 set_capability( "xml", 5 )
92 set_callbacks( Open
, Close
)
95 struct xml_reader_sys_t
97 XTag
*p_root
; /* Root tag */
98 XTag
*p_curtag
; /* Current tag */
99 XList
*p_curattr
; /* Current attribute */
103 static xml_reader_t
*ReaderCreate( xml_t
*, stream_t
* );
104 static void ReaderDelete( xml_reader_t
* );
105 static int ReaderRead( xml_reader_t
* );
106 static int ReaderNodeType( xml_reader_t
* );
107 static char *ReaderName( xml_reader_t
* );
108 static char *ReaderValue( xml_reader_t
* );
109 static int ReaderNextAttr( xml_reader_t
* );
111 static int ReaderUseDTD ( xml_reader_t
*, bool );
113 static void CatalogLoad( xml_t
*, const char * );
114 static void CatalogAdd( xml_t
*, const char *, const char *, const char * );
116 static XTag
*xtag_new_parse( const char *, int );
117 static char *xtag_get_name( XTag
* );
119 static char *xtag_get_pcdata( XTag
* );
120 static char *xtag_get_attribute( XTag
*, char * );
122 static XTag
*xtag_first_child( XTag
*, char * );
123 static XTag
*xtag_next_child( XTag
*, char * );
124 static void xtag_free( XTag
* );
126 static int xtag_snprint( char *, int, XTag
* );
129 /*****************************************************************************
130 * Module initialization
131 *****************************************************************************/
132 static int Open( vlc_object_t
*p_this
)
134 xml_t
*p_xml
= (xml_t
*)p_this
;
136 p_xml
->pf_reader_create
= ReaderCreate
;
137 p_xml
->pf_reader_delete
= ReaderDelete
;
139 p_xml
->pf_catalog_load
= CatalogLoad
;
140 p_xml
->pf_catalog_add
= CatalogAdd
;
145 /*****************************************************************************
146 * Module deinitialization
147 *****************************************************************************/
148 static void Close( vlc_object_t
*p_this
)
154 /*****************************************************************************
155 * Catalogue functions
156 *****************************************************************************/
157 static void CatalogLoad( xml_t
*p_xml
, const char *psz_filename
)
159 VLC_UNUSED(psz_filename
);
160 msg_Dbg( p_xml
, "catalog support not implemented" );
163 static void CatalogAdd( xml_t
*p_xml
, const char *psz_arg1
,
164 const char *psz_arg2
, const char *psz_filename
)
166 VLC_UNUSED(p_xml
); VLC_UNUSED(psz_arg1
); VLC_UNUSED(psz_arg2
);
167 VLC_UNUSED(psz_filename
);
170 /*****************************************************************************
172 *****************************************************************************/
173 static xml_reader_t
*ReaderCreate( xml_t
*p_xml
, stream_t
*s
)
175 xml_reader_t
*p_reader
;
177 int i_size
, i_pos
= 0, i_buffer
= 2048;
180 /* Open and read file */
181 p_buffer
= malloc( i_buffer
);
182 if( p_buffer
== NULL
)
185 while( ( i_size
= stream_Read( s
, &p_buffer
[i_pos
], 2048 ) ) == 2048 )
189 p_buffer
= realloc_or_free( p_buffer
, i_buffer
);
193 if( i_pos
+ i_size
== 0 )
195 msg_Dbg( p_xml
, "empty XML" );
199 p_buffer
[ i_pos
+ i_size
] = '\0'; /* 0 terminated string */
201 p_root
= xtag_new_parse( p_buffer
, i_buffer
);
204 msg_Warn( p_xml
, "couldn't parse XML" );
210 p_reader
= malloc( sizeof(xml_reader_t
) );
213 p_reader
->p_sys
= malloc( sizeof(xml_reader_sys_t
) );
214 if( !p_reader
->p_sys
)
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
;
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
;
235 static void ReaderDelete( xml_reader_t
*p_reader
)
237 xtag_free( p_reader
->p_sys
->p_root
);
238 free( p_reader
->p_sys
);
242 static int ReaderUseDTD ( xml_reader_t
*p_reader
, bool b_use
)
244 VLC_UNUSED(p_reader
); VLC_UNUSED(b_use
);
248 static int ReaderRead( xml_reader_t
*p_reader
)
252 if( !p_reader
->p_sys
->p_curtag
)
254 p_reader
->p_sys
->p_curtag
= p_reader
->p_sys
->p_root
;
260 if( (p_child
= xtag_next_child( p_reader
->p_sys
->p_curtag
, 0 )) )
262 p_reader
->p_sys
->p_curtag
= p_child
;
263 p_reader
->p_sys
->p_curattr
= NULL
;
264 p_reader
->p_sys
->b_endtag
= false;
268 if( p_reader
->p_sys
->p_curtag
->name
&& /* no end tag for pcdata */
269 !p_reader
->p_sys
->b_endtag
)
271 p_reader
->p_sys
->b_endtag
= true;
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
;
283 static int ReaderNodeType( xml_reader_t
*p_reader
)
285 if( p_reader
->p_sys
->p_curtag
->name
&& p_reader
->p_sys
->b_endtag
)
286 return XML_READER_ENDELEM
;
287 if( p_reader
->p_sys
->p_curtag
->name
)
288 return XML_READER_STARTELEM
;
289 if( p_reader
->p_sys
->p_curtag
->pcdata
)
290 return XML_READER_TEXT
;
291 return XML_READER_NONE
;
294 static char *ReaderName( xml_reader_t
*p_reader
)
296 const char *psz_name
;
298 if( !p_reader
->p_sys
->p_curattr
)
300 psz_name
= xtag_get_name( p_reader
->p_sys
->p_curtag
);
302 fprintf( stderr
, "TAG: %s\n", psz_name
);
306 psz_name
= ((XAttribute
*)p_reader
->p_sys
->p_curattr
->data
)->name
;
308 return psz_name
? strdup( psz_name
) : NULL
;
311 static char *ReaderValue( xml_reader_t
*p_reader
)
313 const char *psz_name
;
314 if( p_reader
->p_sys
->p_curtag
->pcdata
)
317 fprintf( stderr
, "%s\n", p_reader
->p_sys
->p_curtag
->pcdata
);
319 return strdup( p_reader
->p_sys
->p_curtag
->pcdata
);
322 if( !p_reader
->p_sys
->p_curattr
) return NULL
;
325 fprintf( stderr
, "%s=%s\n", ((XAttribute
*)p_reader
->p_sys
->p_curattr
->data
)->name
,
326 ((XAttribute
*)p_reader
->p_sys
->p_curattr
->data
)->value
);
329 psz_name
= ((XAttribute
*)p_reader
->p_sys
->p_curattr
->data
)->value
;
331 return psz_name
? strdup( psz_name
) : NULL
;
334 static int ReaderNextAttr( xml_reader_t
*p_reader
)
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
;
341 return p_reader
->p_sys
->p_curattr
? VLC_SUCCESS
: VLC_EGENERIC
;
344 /*****************************************************************************
345 * XTAG parser functions
346 *****************************************************************************/
348 static XList
*xlist_append( XList
*list
, void *data
)
352 l
= (XList
*)xmalloc( sizeof(XList
) );
353 l
->prev
= l
->next
= NULL
;
359 /* Find the last element */
369 static void xlist_free( XList
*list
)
373 for( l
= list
; l
; l
= ln
)
380 /* Character classes */
382 #define X_WHITESPACE 1<<0
383 #define X_OPENTAG 1<<1
384 #define X_CLOSETAG 1<<2
385 #define X_DQUOTE 1<<3
386 #define X_SQUOTE 1<<4
393 static int xtag_cin( char c
, int char_class
)
395 if( char_class
& X_WHITESPACE
) if( isspace(c
) ) return true;
396 if( char_class
& X_OPENTAG
) if( c
== '<' ) return true;
397 if( char_class
& X_CLOSETAG
) if( c
== '>' ) return true;
398 if( char_class
& X_DQUOTE
) if( c
== '"' ) return true;
399 if( char_class
& X_SQUOTE
) if( c
== '\'' ) return true;
400 if( char_class
& X_EQUAL
) if( c
== '=' ) return true;
401 if( char_class
& X_SLASH
) if( c
== '/' ) return true;
402 if( char_class
& X_QMARK
) if( c
== '?' ) return true;
403 if( char_class
& X_DASH
) if( c
== '-' ) return true;
404 if( char_class
& X_EMARK
) if( c
== '!' ) return true;
409 static int xtag_index( XTagParser
*parser
, int char_class
)
411 char *s
= parser
->start
;
414 for( i
= 0; s
[i
] && s
!= parser
->end
; i
++ )
416 if( xtag_cin( s
[i
], char_class
) ) return i
;
422 static void xtag_skip_over( XTagParser
*parser
, int char_class
)
424 char *s
= parser
->start
;
427 if( !parser
->valid
) return;
429 for( i
= 0; s
[i
] && s
!= parser
->end
; i
++ )
431 if( !xtag_cin( s
[i
], char_class
) )
433 parser
->start
= &s
[i
];
441 static void xtag_skip_whitespace( XTagParser
* parser
)
443 xtag_skip_over( parser
, X_WHITESPACE
);
446 static char *xtag_slurp_to( XTagParser
*parser
, int good_end
, int bad_end
)
448 char *ret
, *s
= parser
->start
;
451 if( !parser
->valid
) return NULL
;
453 xi
= xtag_index( parser
, good_end
| bad_end
);
455 if( xi
> 0 && xtag_cin (s
[xi
], good_end
) )
457 ret
= xmalloc( xi
+1 );
458 strncpy( ret
, s
, xi
);
460 parser
->start
= &s
[xi
];
467 static int xtag_assert_and_pass( XTagParser
*parser
, int char_class
)
469 char *s
= parser
->start
;
471 if( !parser
->valid
) return false;
473 if( !xtag_cin( s
[0], char_class
) )
475 parser
->valid
= false;
479 parser
->start
= &s
[1];
484 static char *xtag_slurp_quoted( XTagParser
*parser
)
487 int quote
= X_DQUOTE
; /* quote char to match on */
490 if( !parser
->valid
) return NULL
;
492 xtag_skip_whitespace( parser
);
496 if( xtag_cin( s
[0], X_SQUOTE
) ) quote
= X_SQUOTE
;
498 if( !xtag_assert_and_pass( parser
, quote
) ) return NULL
;
502 for( xi
= 0; s
[xi
]; xi
++ )
504 if( xtag_cin( s
[xi
], quote
) )
506 if( !(xi
> 1 && s
[xi
-1] == '\\') ) break;
510 ret
= xmalloc( xi
+1 );
511 strncpy( ret
, s
, xi
);
513 parser
->start
= &s
[xi
];
515 if( !xtag_assert_and_pass( parser
, quote
) )
524 static XAttribute
*xtag_parse_attribute( XTagParser
*parser
)
533 xtag_skip_whitespace( parser
);
535 name
= xtag_slurp_to( parser
, X_WHITESPACE
|X_EQUAL
, X_SLASH
|X_CLOSETAG
);
539 xtag_skip_whitespace( parser
);
542 if( !xtag_assert_and_pass( parser
, X_EQUAL
) )
545 fprintf( stderr
, "xtag: attr failed EQUAL on <%s>\n", name
);
550 xtag_skip_whitespace( parser
);
552 value
= xtag_slurp_quoted( parser
);
557 fprintf (stderr
, "Got NULL quoted attribute value\n");
562 attr
= xmalloc( sizeof (*attr
) );
569 parser
->valid
= false;
573 static XTag
*xtag_parse_tag( XTagParser
*parser
)
582 if( !parser
->valid
) return NULL
;
586 /* if this starts a comment tag, skip until end */
587 if( (parser
->end
- parser
->start
) > 7 &&
588 xtag_cin( s
[0], X_OPENTAG
) && xtag_cin( s
[1], X_EMARK
) &&
589 xtag_cin( s
[2], X_DASH
) && xtag_cin( s
[3], X_DASH
) )
591 parser
->start
= s
= &s
[4];
592 while( (xi
= xtag_index( parser
, X_DASH
)) >= 0 )
594 parser
->start
= s
= &s
[xi
+1];
595 if( xtag_cin( s
[0], X_DASH
) && xtag_cin( s
[1], X_CLOSETAG
) )
597 parser
->start
= &s
[2];
598 xtag_skip_whitespace( parser
);
599 return xtag_parse_tag( parser
);
605 /* ignore processing instructions '<?' ... '?>' */
606 if( (parser
->end
- parser
->start
) > 4 &&
607 xtag_cin( s
[0], X_OPENTAG
) && xtag_cin( s
[1], X_QMARK
) )
609 parser
->start
= s
= &s
[2];
610 while ((xi
= xtag_index( parser
, X_QMARK
)) >= 0) {
611 if (xtag_cin( s
[xi
+1], X_CLOSETAG
)) {
612 parser
->start
= &s
[xi
+2];
613 xtag_skip_whitespace( parser
);
614 return xtag_parse_tag( parser
);
620 /* ignore doctype '<!DOCTYPE' ... '>' */
621 if ( (parser
->end
- parser
->start
) > 8 &&
622 !strncmp( s
, "<!DOCTYPE", 9 ) ) {
623 xi
= xtag_index( parser
, X_CLOSETAG
);
625 parser
->start
= &s
[xi
+1];
626 xtag_skip_whitespace( parser
);
627 return xtag_parse_tag( parser
);
634 if( (pcdata
= xtag_slurp_to( parser
, X_OPENTAG
, X_NONE
)) != NULL
)
636 tag
= xmalloc( sizeof(*tag
) );
638 tag
->pcdata
= pcdata
;
639 tag
->parent
= parser
->current_tag
;
640 tag
->attributes
= NULL
;
641 tag
->children
= NULL
;
642 tag
->current_child
= NULL
;
647 /* if this starts a close tag, return NULL and let the parent take it */
648 if( xtag_cin( s
[0], X_OPENTAG
) && xtag_cin( s
[1], X_SLASH
) )
651 /* parse CDATA content */
652 if ( (parser
->end
- parser
->start
) > 8 &&
653 !strncmp( s
, "<![CDATA[", 9 ) ) {
654 parser
->start
= s
= &s
[9];
655 while (parser
->end
- s
> 2) {
656 if (strncmp( s
, "]]>", 3 ) == 0) {
657 if ( !(tag
= malloc( sizeof(*tag
))) ) return NULL
;
658 if ( !(pcdata
= malloc( s
- parser
->start
+ 1)) )
663 strncpy( pcdata
, parser
->start
, s
- parser
->start
);
664 pcdata
[s
- parser
->start
]='\0';
665 parser
->start
= &s
[3];
667 tag
->pcdata
= pcdata
;
668 tag
->parent
= parser
->current_tag
;
669 tag
->attributes
= NULL
;
670 tag
->children
= NULL
;
671 tag
->current_child
= NULL
;
681 if( !xtag_assert_and_pass( parser
, X_OPENTAG
) ) return NULL
;
683 name
= xtag_slurp_to( parser
, X_WHITESPACE
|X_SLASH
|X_CLOSETAG
, X_NONE
);
684 if( name
== NULL
) return NULL
;
687 fprintf (stderr
, "<%s ...\n", name
);
690 tag
= xmalloc( sizeof(*tag
) );
693 tag
->parent
= parser
->current_tag
;
694 tag
->attributes
= NULL
;
695 tag
->children
= NULL
;
696 tag
->current_child
= NULL
;
700 if( xtag_cin( s
[0], X_WHITESPACE
) )
702 while( (attr
= xtag_parse_attribute( parser
)) != NULL
)
704 tag
->attributes
= xlist_append( tag
->attributes
, attr
);
708 xtag_skip_whitespace( parser
);
712 if( xtag_cin( s
[0], X_CLOSETAG
) )
714 parser
->current_tag
= tag
;
716 xtag_assert_and_pass( parser
, X_CLOSETAG
);
718 while( (inner
= xtag_parse_tag( parser
) ) != NULL
)
720 tag
->children
= xlist_append( tag
->children
, inner
);
723 parser
->current_tag
= tag
->parent
;
724 xtag_skip_whitespace( parser
);
726 xtag_assert_and_pass( parser
, X_OPENTAG
);
727 xtag_assert_and_pass( parser
, X_SLASH
);
728 name
= xtag_slurp_to( parser
, X_WHITESPACE
| X_CLOSETAG
, X_NONE
);
731 if( strcmp( name
, tag
->name
) )
734 fprintf (stderr
, "got %s expected %s\n", name
, tag
->name
);
736 parser
->valid
= false;
741 xtag_skip_whitespace( parser
);
742 xtag_assert_and_pass( parser
, X_CLOSETAG
);
743 xtag_skip_whitespace( parser
);
747 xtag_assert_and_pass( parser
, X_SLASH
);
748 xtag_assert_and_pass( parser
, X_CLOSETAG
);
749 xtag_skip_whitespace( parser
);
755 static void xtag_free( XTag
*xtag
)
765 free( xtag
->pcdata
);
767 for( l
= xtag
->attributes
; l
; l
= l
->next
)
769 if( (attr
= (XAttribute
*)l
->data
) != NULL
)
776 xlist_free( xtag
->attributes
);
778 for( l
= xtag
->children
; l
; l
= l
->next
)
780 child
= (XTag
*)l
->data
;
783 xlist_free( xtag
->children
);
788 static XTag
*xtag_new_parse( const char *s
, int n
)
791 XTag
*tag
, *ttag
, *wrapper
;
794 parser
.current_tag
= NULL
;
795 parser
.start
= (char *)s
;
797 if( n
== -1 ) parser
.end
= NULL
;
801 fprintf (stderr
, "empty buffer\n");
805 else parser
.end
= (char *)&s
[n
];
807 /* can't have whitespace pcdata outside rootnode */
808 xtag_skip_whitespace( &parser
);
810 tag
= xtag_parse_tag( &parser
);
815 fprintf (stderr
, "invalid file\n");
821 if( (ttag
= xtag_parse_tag( &parser
)) != NULL
)
829 wrapper
= xmalloc( sizeof(XTag
) );
830 wrapper
->name
= NULL
;
831 wrapper
->pcdata
= NULL
;
832 wrapper
->parent
= NULL
;
833 wrapper
->attributes
= NULL
;
834 wrapper
->children
= NULL
;
835 wrapper
->current_child
= NULL
;
837 wrapper
->children
= xlist_append( wrapper
->children
, tag
);
838 wrapper
->children
= xlist_append( wrapper
->children
, ttag
);
840 while( (ttag
= xtag_parse_tag( &parser
)) != NULL
)
848 wrapper
->children
= xlist_append( wrapper
->children
, ttag
);
856 static char *xtag_get_name( XTag
*xtag
)
858 return xtag
? xtag
->name
: NULL
;
862 static char *xtag_get_pcdata( XTag
*xtag
)
867 if( xtag
== NULL
) return NULL
;
869 for( l
= xtag
->children
; l
; l
= l
->next
)
871 child
= (XTag
*)l
->data
;
872 if( child
->pcdata
!= NULL
)
874 return child
->pcdata
;
881 static char *xtag_get_attribute( XTag
*xtag
, char *attribute
)
886 if( xtag
== NULL
) return NULL
;
888 for( l
= xtag
->attributes
; l
; l
= l
->next
)
890 if( (attr
= (XAttribute
*)l
->data
) != NULL
)
892 if( !strcmp( attr
->name
, attribute
) ) return attr
->value
;
900 static XTag
*xtag_first_child( XTag
*xtag
, char *name
)
905 if( xtag
== NULL
) return NULL
;
906 if( (l
= xtag
->children
) == NULL
) return NULL
;
910 xtag
->current_child
= l
;
911 return (XTag
*)l
->data
;
914 for( ; l
; l
= l
->next
)
916 child
= (XTag
*)l
->data
;
918 if( !strcmp( child
->name
, name
) )
920 xtag
->current_child
= l
;
925 xtag
->current_child
= NULL
;
930 static XTag
*xtag_next_child( XTag
*xtag
, char *name
)
935 if( xtag
== NULL
) return NULL
;
937 if( (l
= xtag
->current_child
) == NULL
)
938 return xtag_first_child( xtag
, name
);
940 if( (l
= l
->next
) == NULL
) return NULL
;
944 xtag
->current_child
= l
;
945 return (XTag
*)l
->data
;
948 for( ; l
; l
= l
->next
)
950 child
= (XTag
*)l
->data
;
952 if( !strcmp( child
->name
, name
) )
954 xtag
->current_child
= l
;
959 xtag
->current_child
= NULL
;
966 * This snprints function takes a variable list of char *, the last of
967 * which must be NULL, and prints each in turn to buf.
968 * Returns C99-style total length that would have been written, even if
969 * this is larger than n.
971 static int xtag_snprints( char *buf
, int n
, ... )
975 int len
, to_copy
, total
= 0;
979 for( s
= va_arg( ap
, char * ); s
; s
= va_arg( ap
, char *) )
983 if( (to_copy
= __MIN(n
, len
) ) > 0 )
985 memcpy( buf
, s
, to_copy
);
998 static int xtag_snprint( char *buf
, int n
, XTag
*xtag
)
1000 int nn
, written
= 0;
1005 #define FORWARD(N) \
1006 buf += __MIN(n, N); \
1007 n = __MAX(n-N, 0); \
1012 if( n
> 0 ) buf
[0] = '\0';
1018 nn
= xtag_snprints( buf
, n
, xtag
->pcdata
, NULL
);
1026 nn
= xtag_snprints( buf
, n
, "<", xtag
->name
, NULL
);
1029 for( l
= xtag
->attributes
; l
; l
= l
->next
)
1031 attr
= (XAttribute
*)l
->data
;
1033 nn
= xtag_snprints( buf
, n
, " ", attr
->name
, "=\"", attr
->value
,
1038 if( xtag
->children
== NULL
)
1040 nn
= xtag_snprints ( buf
, n
, "/>", NULL
);
1046 nn
= xtag_snprints( buf
, n
, ">", NULL
);
1050 for( l
= xtag
->children
; l
; l
= l
->next
)
1052 child
= (XTag
*)l
->data
;
1054 nn
= xtag_snprint( buf
, n
, child
);
1060 nn
= xtag_snprints( buf
, n
, "</", xtag
->name
, ">", NULL
);