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