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