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