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