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