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