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