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