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