]> git.sesse.net Git - vlc/blob - modules/demux/playlist/xspf.c
xspf demux: fix tree-parsing
[vlc] / modules / demux / playlist / xspf.c
1 /*******************************************************************************
2  * xspf.c : XSPF playlist import functions
3  *******************************************************************************
4  * Copyright (C) 2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Daniel Stränger <vlc at schmaller dot de>
8  *          Yoann Peronneau <yoann@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  ******************************************************************************/
24 /**
25  * \file modules/demux/playlist/xspf.c
26  * \brief XSPF playlist import functions
27  */
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc_common.h>
34 #include <vlc_demux.h>
35
36 #include <vlc_xml.h>
37 #include <vlc_strings.h>
38 #include <vlc_url.h>
39 #include "xspf.h"
40 #include "playlist.h"
41
42 struct demux_sys_t
43 {
44     input_item_t **pp_tracklist;
45     int i_tracklist_entries;
46     int i_track_id;
47     char * psz_base;
48 };
49
50 static int Control( demux_t *, int, va_list );
51 static int Demux( demux_t * );
52
53 /**
54  * \brief XSPF submodule initialization function
55  */
56 int Import_xspf( vlc_object_t *p_this )
57 {
58     DEMUX_BY_EXTENSION_OR_FORCED_MSG( ".xspf", "xspf-open",
59                                       "using XSPF playlist reader" );
60     return VLC_SUCCESS;
61 }
62
63 void Close_xspf( vlc_object_t *p_this )
64 {
65     demux_t *p_demux = (demux_t *)p_this;
66     demux_sys_t *p_sys = p_demux->p_sys;
67     for( int i = 0; i < p_sys->i_tracklist_entries; i++ )
68     {
69         if( p_sys->pp_tracklist[i] )
70             vlc_gc_decref( p_sys->pp_tracklist[i] );
71     }
72     free( p_sys->pp_tracklist );
73     free( p_sys->psz_base );
74     free( p_sys );
75 }
76
77 /**
78  * \brief demuxer function for XSPF parsing
79  */
80 int Demux( demux_t *p_demux )
81 {
82     int i_ret = -1;
83     xml_reader_t *p_xml_reader = NULL;
84     char *psz_name = NULL;
85     input_item_t *p_current_input = GetCurrentItem(p_demux);
86     p_demux->p_sys->pp_tracklist = NULL;
87     p_demux->p_sys->i_tracklist_entries = 0;
88     p_demux->p_sys->i_track_id = -1;
89     p_demux->p_sys->psz_base = NULL;
90
91     /* create new xml parser from stream */
92     p_xml_reader = xml_ReaderCreate( p_demux, p_demux->s );
93     if( !p_xml_reader )
94         goto end;
95
96     /* locating the root node */
97     if( xml_ReaderNextNode( p_xml_reader ) != XML_READER_STARTELEM )
98     {
99         msg_Err( p_demux, "can't read xml stream" );
100         goto end;
101     }
102
103     /* checking root node name */
104     psz_name = xml_ReaderName( p_xml_reader );
105     if( !psz_name || strcmp( psz_name, "playlist" ) )
106     {
107         msg_Err( p_demux, "invalid root node name: %s", psz_name );
108         free( psz_name );
109         goto end;
110     }
111     free( psz_name );
112
113     input_item_node_t *p_subitems =
114         input_item_node_Create( p_current_input );
115
116     i_ret = parse_playlist_node( p_demux, p_subitems,
117                                  p_xml_reader, "playlist" ) ? 0 : -1;
118
119     for( int i = 0 ; i < p_demux->p_sys->i_tracklist_entries ; i++ )
120     {
121         input_item_t *p_new_input = p_demux->p_sys->pp_tracklist[i];
122         if( p_new_input )
123         {
124             input_item_node_AppendItem( p_subitems, p_new_input );
125         }
126     }
127
128     input_item_node_PostAndDelete( p_subitems );
129
130 end:
131     vlc_gc_decref(p_current_input);
132     if( p_xml_reader )
133         xml_ReaderDelete( p_xml_reader );
134     return i_ret; /* Needed for correct operation of go back */
135 }
136
137 /** \brief dummy function for demux callback interface */
138 static int Control( demux_t *p_demux, int i_query, va_list args )
139 {
140     VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
141     return VLC_EGENERIC;
142 }
143
144 /**
145  * \brief parse the root node of a XSPF playlist
146  * \param p_demux demuxer instance
147  * \param p_input_item current input item
148  * \param p_xml_reader xml reader instance
149  * \param psz_element name of element to parse
150  */
151 static bool parse_playlist_node COMPLEX_INTERFACE
152 {
153     input_item_t *p_input_item = p_input_node->p_item;
154     char *psz_name = NULL;
155     char *psz_value = NULL;
156     bool b_version_found = false;
157     int i_node;
158     bool b_ret = false;
159     xml_elem_hnd_t *p_handler = NULL;
160
161     xml_elem_hnd_t pl_elements[] =
162         { {"title",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
163           {"creator",      SIMPLE_CONTENT,  {.smpl = set_item_info} },
164           {"annotation",   SIMPLE_CONTENT,  {.smpl = set_item_info} },
165           {"info",         SIMPLE_CONTENT,  {NULL} },
166           {"location",     SIMPLE_CONTENT,  {NULL} },
167           {"identifier",   SIMPLE_CONTENT,  {NULL} },
168           {"image",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
169           {"date",         SIMPLE_CONTENT,  {NULL} },
170           {"license",      SIMPLE_CONTENT,  {NULL} },
171           {"attribution",  COMPLEX_CONTENT, {.cmplx = skip_element} },
172           {"link",         SIMPLE_CONTENT,  {NULL} },
173           {"meta",         SIMPLE_CONTENT,  {NULL} },
174           {"extension",    COMPLEX_CONTENT, {.cmplx = parse_extension_node} },
175           {"trackList",    COMPLEX_CONTENT, {.cmplx = parse_tracklist_node} },
176           {NULL,           UNKNOWN_CONTENT, {NULL} }
177         };
178
179     /* read all playlist attributes */
180     while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
181     {
182         psz_name = xml_ReaderName( p_xml_reader );
183         psz_value = xml_ReaderValue( p_xml_reader );
184         if( !psz_name || !psz_value )
185         {
186             msg_Err( p_demux, "invalid xml stream @ <playlist>" );
187             goto end;
188         }
189         /* attribute: version */
190         if( !strcmp( psz_name, "version" ) )
191         {
192             b_version_found = true;
193             if( strcmp( psz_value, "0" ) && strcmp( psz_value, "1" ) )
194                 msg_Warn( p_demux, "unsupported XSPF version" );
195         }
196         /* attribute: xmlns */
197         else if( !strcmp( psz_name, "xmlns" ) || !strcmp( psz_name, "xmlns:vlc" ) )
198             ;
199         else if( !strcmp( psz_name, "xml:base" ) )
200         {
201             p_demux->p_sys->psz_base = strdup( psz_value );
202         }
203         /* unknown attribute */
204         else
205             msg_Warn( p_demux, "invalid <playlist> attribute:\"%s\"", psz_name);
206
207         free( psz_name );
208         free( psz_value );
209     }
210     /* attribute version is mandatory !!! */
211     if( !b_version_found )
212         msg_Warn( p_demux, "<playlist> requires \"version\" attribute" );
213
214     /* parse the child elements - we only take care of <trackList> */
215     psz_name = NULL;
216     psz_value = NULL;
217     while( (i_node = xml_ReaderNextNode( p_xml_reader )) > 0 )
218     {
219         switch( i_node )
220         {
221             case XML_READER_STARTELEM:
222                 /*  element start tag  */
223                 psz_name = xml_ReaderName( p_xml_reader );
224                 if( !psz_name || !*psz_name )
225                 {
226                     msg_Err( p_demux, "invalid xml stream" );
227                     goto end;
228                 }
229                 /* choose handler */
230                 for( p_handler = pl_elements;
231                      p_handler->name && strcmp( psz_name, p_handler->name );
232                      p_handler++ );
233                 if( !p_handler->name )
234                 {
235                     msg_Err( p_demux, "unexpected element <%s>", psz_name );
236                     goto end;
237                 }
238                 FREE_NAME();
239                 /* complex content is parsed in a separate function */
240                 if( p_handler->type == COMPLEX_CONTENT )
241                 {
242                     FREE_VALUE();
243                     if( p_handler->pf_handler.cmplx( p_demux,
244                                                      p_input_node,
245                                                      p_xml_reader,
246                                                      p_handler->name ) )
247                     {
248                         p_handler = NULL;
249                     }
250                     else
251                     {
252                         return false;
253                     }
254                 }
255                 break;
256
257             case XML_READER_TEXT:
258                 /* simple element content */
259                 free( psz_value );
260                 psz_value = xml_ReaderValue( p_xml_reader );
261                 if( !psz_value )
262                 {
263                     msg_Err( p_demux, "invalid xml stream" );
264                     goto end;
265                 }
266                 break;
267
268             case XML_READER_ENDELEM:
269                 /* element end tag */
270                 psz_name = xml_ReaderName( p_xml_reader );
271                 if( !psz_name )
272                 {
273                     msg_Err( p_demux, "invalid xml stream" );
274                     goto end;
275                 }
276                 /* leave if the current parent node <playlist> is terminated */
277                 if( !strcmp( psz_name, psz_element ) )
278                 {
279                     b_ret = true;
280                     goto end;
281                 }
282                 /* there MUST have been a start tag for that element name */
283                 if( !p_handler || !p_handler->name
284                     || strcmp( p_handler->name, psz_name ))
285                 {
286                     msg_Err( p_demux, "there's no open element left for <%s>",
287                              psz_name );
288                     goto end;
289                 }
290
291                 if( p_handler->pf_handler.smpl )
292                 {
293                     p_handler->pf_handler.smpl( p_input_item, p_handler->name,
294                                                 psz_value );
295                 }
296                 FREE_ATT();
297                 p_handler = NULL;
298                 break;
299         }
300     }
301
302 end:
303     free( psz_name );
304     free( psz_value );
305     return b_ret;
306 }
307
308 /**
309  * \brief parses the tracklist node which only may contain <track>s
310  */
311 static bool parse_tracklist_node COMPLEX_INTERFACE
312 {
313     VLC_UNUSED(psz_element);
314     char *psz_name;
315     int i_ntracks = 0;
316     int i_node;
317
318     /* now parse the <track>s */
319     while( (i_node = xml_ReaderNextNode( p_xml_reader )) > 0 )
320     {
321         if( i_node == XML_READER_STARTELEM )
322         {
323             char *psz_eltname = xml_ReaderName( p_xml_reader );
324             if( !psz_eltname )
325             {
326                 msg_Err( p_demux, "unexpected end of xml data" );
327                 free( psz_eltname );
328                 return false;
329             }
330             if( strcmp( psz_eltname, "track") )
331             {
332                 msg_Err( p_demux, "unexpected child of <trackList>: <%s>",
333                          psz_eltname );
334                 free( psz_eltname );
335                 return false;
336             }
337             free( psz_eltname );
338
339             /* parse the track data in a separate function */
340             if( parse_track_node( p_demux, p_input_node,
341                                   p_xml_reader, "track" ) )
342                 i_ntracks++;
343         }
344         else if( i_node == XML_READER_ENDELEM )
345             break;
346     }
347
348     /* the <trackList> has to be terminated */
349     if( i_node != XML_READER_ENDELEM )
350     {
351         msg_Err( p_demux, "there's a missing </trackList>" );
352         return false;
353     }
354     psz_name = xml_ReaderName( p_xml_reader );
355     if( !psz_name || strcmp( psz_name, "trackList" ) )
356     {
357         msg_Err( p_demux, "expected: </trackList>, found: </%s>", psz_name );
358         free( psz_name );
359         return false;
360     }
361     free( psz_name );
362
363     msg_Dbg( p_demux, "parsed %i tracks successfully", i_ntracks );
364     return true;
365 }
366
367 /**
368  * \brief parse one track element
369  * \param COMPLEX_INTERFACE
370  */
371 static bool parse_track_node COMPLEX_INTERFACE
372 {
373     input_item_t *p_input_item = p_input_node->p_item;
374     char *psz_name = NULL;
375     char *psz_value = NULL;
376     xml_elem_hnd_t *p_handler = NULL;
377     demux_sys_t *p_sys = p_demux->p_sys;
378     int i_node;
379     bool b_ret = false;
380
381     xml_elem_hnd_t track_elements[] =
382         { {"location",     SIMPLE_CONTENT,  {NULL} },
383           {"identifier",   SIMPLE_CONTENT,  {NULL} },
384           {"title",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
385           {"creator",      SIMPLE_CONTENT,  {.smpl = set_item_info} },
386           {"annotation",   SIMPLE_CONTENT,  {.smpl = set_item_info} },
387           {"info",         SIMPLE_CONTENT,  {NULL} },
388           {"image",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
389           {"album",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
390           {"trackNum",     SIMPLE_CONTENT,  {.smpl = set_item_info} },
391           {"duration",     SIMPLE_CONTENT,  {.smpl = set_item_info} },
392           {"link",         SIMPLE_CONTENT,  {NULL} },
393           {"meta",         SIMPLE_CONTENT,  {NULL} },
394           {"extension",    COMPLEX_CONTENT, {.cmplx = parse_extension_node} },
395           {NULL,           UNKNOWN_CONTENT, {NULL} }
396         };
397
398     input_item_t *p_new_input = input_item_New( p_demux, NULL, NULL );
399     input_item_node_t *p_new_node = input_item_node_Create( p_new_input );
400
401     if( !p_new_input )
402     {
403         /* malloc has failed for input_item_New, so bailout early */
404         return false;
405     }
406
407     /* reset i_track_id */
408     p_sys->i_track_id = -1;
409
410     while( (i_node = xml_ReaderNextNode( p_xml_reader )) > 0 )
411     {
412         switch( i_node )
413         {
414             case XML_READER_STARTELEM:
415                 /*  element start tag  */
416                 psz_name = xml_ReaderName( p_xml_reader );
417                 if( !psz_name || !*psz_name )
418                 {
419                     msg_Err( p_demux, "invalid xml stream" );
420                     goto end;
421                 }
422                 /* choose handler */
423                 for( p_handler = track_elements;
424                      p_handler->name && strcmp( psz_name, p_handler->name );
425                      p_handler++ );
426                 if( !p_handler->name )
427                 {
428                     msg_Err( p_demux, "unexpected element <%s>", psz_name );
429                     goto end;
430                 }
431                 FREE_NAME();
432                 /* complex content is parsed in a separate function */
433                 if( p_handler->type == COMPLEX_CONTENT )
434                 {
435                     FREE_VALUE();
436
437                     bool b_res = p_handler->pf_handler.cmplx( p_demux,
438                                                         p_new_node,
439                                                         p_xml_reader,
440                                                         p_handler->name );
441                     if( b_res )
442                     {
443                         p_handler = NULL;
444                     }
445                     else
446                     {
447                         return false;
448                     }
449                 }
450                 break;
451
452             case XML_READER_TEXT:
453                 /* simple element content */
454                 free( psz_value );
455                 psz_value = xml_ReaderValue( p_xml_reader );
456                 if( !psz_value )
457                 {
458                     msg_Err( p_demux, "invalid xml stream" );
459                     goto end;
460                 }
461                 break;
462
463             case XML_READER_ENDELEM:
464                 /* element end tag */
465                 psz_name = xml_ReaderName( p_xml_reader );
466                 if( !psz_name )
467                 {
468                     msg_Err( p_demux, "invalid xml stream" );
469                     goto end;
470                 }
471
472                 /* leave if the current parent node <track> is terminated */
473                 if( !strcmp( psz_name, psz_element ) )
474                 {
475                     free( psz_name );
476                     free( psz_value );
477
478                     /* Make sure we have a URI */
479                     char *psz_uri = input_item_GetURI( p_new_input );
480                     if( !psz_uri )
481                     {
482                         input_item_SetURI( p_new_input, "vlc://nop" );
483                     }
484                     free( psz_uri );
485
486                     if( p_sys->i_track_id < 0 )
487                     {
488                         input_item_node_AppendNode( p_input_node, p_new_node );
489                         vlc_gc_decref( p_new_input );
490                         return true;
491                     }
492
493                     if( p_sys->i_track_id >= p_sys->i_tracklist_entries )
494                     {
495                         input_item_t **pp;
496                         pp = realloc( p_sys->pp_tracklist,
497                             (p_sys->i_track_id + 1) * sizeof(*pp) );
498                         if( !pp )
499                             return false;
500                         p_sys->pp_tracklist = pp;
501                         while( p_sys->i_track_id >= p_sys->i_tracklist_entries )
502                             pp[p_sys->i_tracklist_entries++] = NULL;
503                     }
504
505                     p_sys->pp_tracklist[ p_sys->i_track_id ] = p_new_input;
506                     return true;
507                 }
508                 /* there MUST have been a start tag for that element name */
509                 if( !p_handler || !p_handler->name
510                     || strcmp( p_handler->name, psz_name ))
511                 {
512                     msg_Err( p_demux, "there's no open element left for <%s>",
513                              psz_name );
514                     goto end;
515                 }
516
517                 /* special case: location */
518                 if( !strcmp( p_handler->name, "location" ) )
519                 {
520                     if( psz_value == NULL )
521                         input_item_SetURI( p_new_input, "vlc://nop" );
522                     else
523                     /* FIXME: This is broken. Scheme-relative (//...) locations
524                      * and anchors (#...) are not resolved correctly. Also,
525                      * host-relative (/...) and directory-relative locations
526                      * ("relative path" in vernacular) should be resolved.
527                      * Last, psz_base should default to the XSPF resource
528                      * location if missing (not the current working directory).
529                      * -- Courmisch */
530                     if( p_sys->psz_base && !strstr( psz_value, "://" ) )
531                     {
532                         char* psz_tmp;
533                         if( asprintf( &psz_tmp, "%s%s", p_sys->psz_base,
534                                       psz_value ) == -1 )
535                         {
536                             goto end;
537                         }
538                         input_item_SetURI( p_new_input, psz_tmp );
539                         free( psz_tmp );
540                     }
541                     else
542                         input_item_SetURI( p_new_input, psz_value );
543                     input_item_CopyOptions( p_input_item, p_new_input );
544                 }
545                 else
546                 {
547                     /* there MUST be an item */
548                     if( p_handler->pf_handler.smpl )
549                     {
550                         p_handler->pf_handler.smpl( p_new_input,
551                                                     p_handler->name,
552                                                     psz_value );
553                         FREE_ATT();
554                     }
555                 }
556                 FREE_ATT();
557                 p_handler = NULL;
558                 break;
559         }
560     }
561     msg_Err( p_demux, "unexpected end of xml data" );
562
563 end:
564     free( psz_name );
565     free( psz_value );
566     return b_ret;
567 }
568
569 /**
570  * \brief handles the supported <track> sub-elements
571  */
572 static bool set_item_info SIMPLE_INTERFACE
573 {
574     /* exit if setting is impossible */
575     if( !psz_name || !psz_value || !p_input )
576         return false;
577
578     /* re-convert xml special characters inside psz_value */
579     resolve_xml_special_chars( psz_value );
580
581     /* handle each info element in a separate "if" clause */
582     if( !strcmp( psz_name, "title" ) )
583     {
584         input_item_SetTitle( p_input, psz_value );
585     }
586     else if( !strcmp( psz_name, "creator" ) )
587     {
588         input_item_SetArtist( p_input, psz_value );
589     }
590     else if( !strcmp( psz_name, "album" ) )
591     {
592         input_item_SetAlbum( p_input, psz_value );
593
594     }
595     else if( !strcmp( psz_name, "trackNum" ) )
596     {
597         input_item_SetTrackNum( p_input, psz_value );
598     }
599     else if( !strcmp( psz_name, "duration" ) )
600     {
601         long i_num = atol( psz_value );
602         input_item_SetDuration( p_input, (mtime_t) i_num*1000 );
603     }
604     else if( !strcmp( psz_name, "annotation" ) )
605     {
606         input_item_SetDescription( p_input, psz_value );
607     }
608     else if( !strcmp( psz_name, "image" ) )
609     {
610         input_item_SetArtURL( p_input, psz_value );
611     }
612     return true;
613 }
614
615 /**
616  * \brief handles the <vlc:option> elements
617  */
618 static bool set_option SIMPLE_INTERFACE
619 {
620     /* exit if setting is impossible */
621     if( !psz_name || !psz_value || !p_input )
622         return false;
623
624     /* re-convert xml special characters inside psz_value */
625     resolve_xml_special_chars( psz_value );
626
627     input_item_AddOption( p_input, psz_value, 0 );
628
629     return true;
630 }
631
632 /**
633  * \brief parse the extension node of a XSPF playlist
634  */
635 static bool parse_extension_node COMPLEX_INTERFACE
636 {
637     input_item_t *p_input_item = p_input_node->p_item;
638     char *psz_name = NULL;
639     char *psz_value = NULL;
640     char *psz_title = NULL;
641     char *psz_application = NULL;
642     int i_node;
643     bool b_release_input_item = false;
644     xml_elem_hnd_t *p_handler = NULL;
645     input_item_t *p_new_input = NULL;
646
647     xml_elem_hnd_t pl_elements[] =
648         { {"vlc:node",   COMPLEX_CONTENT, {.cmplx = parse_extension_node} },
649           {"vlc:item",   COMPLEX_CONTENT, {.cmplx = parse_extitem_node} },
650           {"vlc:id",     SIMPLE_CONTENT, {NULL} },
651           {"vlc:option", SIMPLE_CONTENT, {.smpl = set_option} },
652           {NULL,    UNKNOWN_CONTENT, {NULL} }
653         };
654
655     /* read all extension node attributes */
656     while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
657     {
658         psz_name = xml_ReaderName( p_xml_reader );
659         psz_value = xml_ReaderValue( p_xml_reader );
660         if( !psz_name || !psz_value )
661         {
662             msg_Err( p_demux, "invalid xml stream @ <vlc:node>" );
663             FREE_ATT();
664             return false;
665         }
666         /* attribute: title */
667         if( !strcmp( psz_name, "title" ) )
668         {
669             resolve_xml_special_chars( psz_value );
670             psz_title = psz_value;
671         }
672         /* extension attribute: application */
673         else if( !strcmp( psz_name, "application" ) )
674         {
675             psz_application = psz_value;
676         }
677         /* unknown attribute */
678         else
679         {
680             msg_Warn( p_demux, "invalid <%s> attribute:\"%s\"", psz_element,
681                       psz_name );
682             FREE_VALUE();
683         }
684         FREE_NAME();
685         psz_value = NULL;
686     }
687
688     /* attribute title is mandatory except for <extension> */
689     if( !strcmp( psz_element, "vlc:node" ) )
690     {
691         if( !psz_title )
692         {
693             msg_Warn( p_demux, "<vlc:node> requires \"title\" attribute" );
694             return false;
695         }
696         p_new_input = input_item_NewWithType( VLC_OBJECT( p_demux ),
697                           "vlc://nop", psz_title, 0, NULL, 0, -1,
698                           ITEM_TYPE_DIRECTORY );
699         if( p_new_input )
700         {
701             p_input_node =
702                 input_item_node_AppendItem( p_input_node, p_new_input );
703             p_input_item = p_new_input;
704             b_release_input_item = true;
705         }
706         free( psz_title );
707     }
708     else if( !strcmp( psz_element, "extension" ) )
709     {
710         if( !psz_application )
711         {
712             msg_Warn( p_demux, "<extension> requires \"application\" attribute" );
713             return false;
714         }
715         /* Skip the extension if the application is not vlc
716            This will skip all children of the current node */
717         else if( strcmp( psz_application, "http://www.videolan.org/vlc/playlist/0" ) )
718         {
719             msg_Dbg( p_demux, "Skipping \"%s\" extension tag", psz_application );
720             free( psz_application );
721             /* Skip all children */
722             for( unsigned lvl = 1; lvl; )
723                 switch( xml_ReaderNextNode( p_xml_reader ) )
724                 {
725                     case XML_READER_STARTELEM: lvl++; break;
726                     case XML_READER_ENDELEM:   lvl--; break;
727                     case 0: case -1: return -1;
728                 }
729             return true;
730         }
731     }
732     free( psz_application );
733
734
735     /* parse the child elements */
736     while( (i_node = xml_ReaderNextNode( p_xml_reader )) > 0 )
737     {
738         switch( i_node )
739         {
740             case XML_READER_STARTELEM:
741                 /*  element start tag  */
742                 psz_name = xml_ReaderName( p_xml_reader );
743                 if( !psz_name || !*psz_name )
744                 {
745                     msg_Err( p_demux, "invalid xml stream" );
746                     FREE_ATT();
747                     if( b_release_input_item ) vlc_gc_decref( p_new_input );
748                     return false;
749                 }
750                 /* choose handler */
751                 for( p_handler = pl_elements;
752                      p_handler->name && strcmp( psz_name, p_handler->name );
753                      p_handler++ );
754                 if( !p_handler->name )
755                 {
756                     msg_Err( p_demux, "unexpected element <%s>", psz_name );
757                     FREE_ATT();
758                     if( b_release_input_item ) vlc_gc_decref( p_new_input );
759                     return false;
760                 }
761                 FREE_NAME();
762                 /* complex content is parsed in a separate function */
763                 if( p_handler->type == COMPLEX_CONTENT )
764                 {
765                     if( p_handler->pf_handler.cmplx( p_demux,
766                                                      p_input_node,
767                                                      p_xml_reader,
768                                                      p_handler->name ) )
769                     {
770                         p_handler = NULL;
771                         FREE_ATT();
772                     }
773                     else
774                     {
775                         FREE_ATT();
776                         if( b_release_input_item ) vlc_gc_decref( p_new_input );
777                         return false;
778                     }
779                 }
780                 break;
781
782             case XML_READER_TEXT:
783                 /* simple element content */
784                 FREE_ATT();
785                 psz_value = xml_ReaderValue( p_xml_reader );
786                 if( !psz_value )
787                 {
788                     msg_Err( p_demux, "invalid xml stream" );
789                     FREE_ATT();
790                     if( b_release_input_item ) vlc_gc_decref( p_new_input );
791                     return false;
792                 }
793                 break;
794
795             case XML_READER_ENDELEM:
796                 /* element end tag */
797                 psz_name = xml_ReaderName( p_xml_reader );
798                 if( !psz_name )
799                 {
800                     msg_Err( p_demux, "invalid xml stream" );
801                     FREE_ATT();
802                     if( b_release_input_item ) vlc_gc_decref( p_new_input );
803                     return false;
804                 }
805                 /* leave if the current parent node is terminated */
806                 if( !strcmp( psz_name, psz_element ) )
807                 {
808                     FREE_ATT();
809                     if( b_release_input_item ) vlc_gc_decref( p_new_input );
810                     return true;
811                 }
812                 /* there MUST have been a start tag for that element name */
813                 if( !p_handler || !p_handler->name
814                     || strcmp( p_handler->name, psz_name ))
815                 {
816                     msg_Err( p_demux, "there's no open element left for <%s>",
817                              psz_name );
818                     FREE_ATT();
819                     if( b_release_input_item ) vlc_gc_decref( p_new_input );
820                     return false;
821                 }
822
823                 /* special tag <vlc:id> */
824                 if( !strcmp( p_handler->name, "vlc:id" ) )
825                 {
826                     p_demux->p_sys->i_track_id = atoi( psz_value );
827                 }
828                 else if( p_handler->pf_handler.smpl )
829                 {
830                     p_handler->pf_handler.smpl( p_input_item, p_handler->name,
831                                                 psz_value );
832                 }
833                 FREE_ATT();
834                 p_handler = NULL;
835                 break;
836         }
837         FREE_NAME();
838     }
839     if( b_release_input_item ) vlc_gc_decref( p_new_input );
840     return false;
841 }
842
843 /**
844  * \brief parse the extension item node of a XSPF playlist
845  */
846 static bool parse_extitem_node COMPLEX_INTERFACE
847 {
848     VLC_UNUSED(psz_element);
849     input_item_t *p_new_input = NULL;
850     int i_tid = -1;
851
852     /* read all extension item attributes */
853     while( xml_ReaderNextAttr( p_xml_reader ) == VLC_SUCCESS )
854     {
855         char *psz_name = xml_ReaderName( p_xml_reader );
856         char *psz_value = xml_ReaderValue( p_xml_reader );
857         if( !psz_name || !psz_value )
858         {
859             msg_Err( p_demux, "invalid xml stream @ <vlc:item>" );
860             free( psz_name );
861             free( psz_value );
862             return false;
863         }
864         /* attribute: href */
865         if( !strcmp( psz_name, "tid" ) )
866         {
867             i_tid = atoi( psz_value );
868         }
869         /* unknown attribute */
870         else
871             msg_Warn( p_demux, "invalid <vlc:item> attribute:\"%s\"", psz_name);
872
873         free( psz_name );
874         free( psz_value );
875     }
876
877     /* attribute href is mandatory */
878     if( i_tid < 0 )
879     {
880         msg_Warn( p_demux, "<vlc:item> requires \"tid\" attribute" );
881         return false;
882     }
883
884     if( i_tid >= p_demux->p_sys->i_tracklist_entries )
885     {
886         msg_Warn( p_demux, "invalid \"tid\" attribute" );
887         return false;
888     }
889
890     p_new_input = p_demux->p_sys->pp_tracklist[ i_tid ];
891     if( p_new_input )
892     {
893         input_item_node_AppendItem( p_input_node, p_new_input );
894         vlc_gc_decref( p_new_input );
895         p_demux->p_sys->pp_tracklist[i_tid] = NULL;
896     }
897
898     return true;
899 }
900
901 /**
902  * \brief skips complex element content that we can't manage
903  */
904 static bool skip_element COMPLEX_INTERFACE
905 {
906     VLC_UNUSED(p_demux); VLC_UNUSED(p_input_node);
907     VLC_UNUSED(psz_element);
908
909     for( unsigned lvl = 1; lvl; )
910         switch( xml_ReaderNextNode( p_xml_reader ) )
911         {
912             case XML_READER_STARTELEM: lvl++; break;
913             case XML_READER_ENDELEM:   lvl--; break;
914             case 0: case -1: return false;
915         }
916
917     return true;
918 }