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