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