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