1 /*******************************************************************************
2 * xspf.c : XSPF playlist import functions
3 *******************************************************************************
4 * Copyright (C) 2006-2011 the VideoLAN team
7 * Authors: Daniel Stränger <vlc at schmaller dot de>
8 * Yoann Peronneau <yoann@videolan.org>
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.
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.
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 ******************************************************************************/
25 * \file modules/demux/playlist/xspf.c
26 * \brief XSPF playlist import functions
33 #include <vlc_common.h>
34 #include <vlc_demux.h>
37 #include <vlc_strings.h>
41 #define FREE_VALUE() do { free(psz_value);psz_value=NULL; } while(0)
43 #define SIMPLE_INTERFACE (input_item_t *p_input,\
44 const char *psz_name,\
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)
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;
67 bool (*smpl) SIMPLE_INTERFACE;
68 bool (*cmplx) COMPLEX_INTERFACE;
74 input_item_t **pp_tracklist;
75 int i_tracklist_entries;
80 static int Control(demux_t *, int, va_list);
81 static int Demux(demux_t *);
84 * \brief XSPF submodule initialization function
86 int Import_xspf(vlc_object_t *p_this)
88 DEMUX_BY_EXTENSION_OR_FORCED_MSG(".xspf", "xspf-open",
89 "using XSPF playlist reader");
93 void Close_xspf(vlc_object_t *p_this)
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);
106 * \brief demuxer function for XSPF parsing
108 static int Demux(demux_t *p_demux)
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;
119 /* create new xml parser from stream */
120 p_xml_reader = xml_ReaderCreate(p_demux, p_demux->s);
124 /* locating the root node */
125 if (xml_ReaderNextNode(p_xml_reader, &name) != XML_READER_STARTELEM)
127 msg_Err(p_demux, "can't read xml stream");
131 /* checking root node name */
132 if (strcmp(name, "playlist"))
134 msg_Err(p_demux, "invalid root node name <%s>", name);
138 input_item_node_t *p_subitems =
139 input_item_node_Create(p_current_input);
141 i_ret = parse_playlist_node(p_demux, p_subitems,
142 p_xml_reader, "playlist") ? 0 : -1;
144 for (int i = 0 ; i < p_demux->p_sys->i_tracklist_entries ; i++)
146 input_item_t *p_new_input = p_demux->p_sys->pp_tracklist[i];
149 input_item_node_AppendItem(p_subitems, p_new_input);
153 input_item_node_PostAndDelete(p_subitems);
156 vlc_gc_decref(p_current_input);
158 xml_ReaderDelete(p_xml_reader);
159 return i_ret; /* Needed for correct operation of go back */
162 /** \brief dummy function for demux callback interface */
163 static int Control(demux_t *p_demux, int i_query, va_list args)
165 VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
169 static const xml_elem_hnd_t *get_handler(const xml_elem_hnd_t *tab, size_t n, const char *name)
171 for (size_t i = 0; i < n / sizeof(xml_elem_hnd_t); i++)
172 if (!strcmp(name, tab[i].name))
176 #define get_handler(tab, name) get_handler(tab, sizeof tab, name)
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
185 static bool parse_playlist_node COMPLEX_INTERFACE
187 input_item_t *p_input_item = p_input_node->p_item;
188 char *psz_value = NULL;
189 bool b_version_found = false;
192 const xml_elem_hnd_t *p_handler = NULL;
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 },
210 /* read all playlist attributes */
211 const char *name, *value;
212 while ((name = xml_ReaderNextAttr(p_xml_reader, &value)) != NULL)
214 /* attribute: version */
215 if (!strcmp(name, "version"))
217 b_version_found = true;
218 if (strcmp(value, "0") && strcmp(value, "1"))
219 msg_Warn(p_demux, "unsupported XSPF version %s", value);
221 /* attribute: xmlns */
222 else if (!strcmp(name, "xmlns") || !strcmp(name, "xmlns:vlc"))
224 else if (!strcmp(name, "xml:base"))
226 free(p_demux->p_sys->psz_base);
227 p_demux->p_sys->psz_base = strdup(psz_value);
229 /* unknown attribute */
231 msg_Warn(p_demux, "invalid <playlist> attribute: \"%s\"", name);
233 /* attribute version is mandatory !!! */
234 if (!b_version_found)
235 msg_Warn(p_demux, "<playlist> requires \"version\" attribute");
237 /* parse the child elements - we only take care of <trackList> */
239 while ((i_node = xml_ReaderNextNode(p_xml_reader, &name)) > 0)
242 /* element start tag */
243 case XML_READER_STARTELEM:
246 msg_Err(p_demux, "invalid XML stream");
250 p_handler = get_handler(pl_elements, name);
253 msg_Err(p_demux, "unexpected element <%s>", name);
256 /* complex content is parsed in a separate function */
257 if (p_handler->cmplx)
260 if (!p_handler->pf_handler.cmplx(p_demux, p_input_node,
261 p_xml_reader, p_handler->name))
267 /* simple element content */
268 case XML_READER_TEXT:
269 psz_value = strdup(name);
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))
282 /* there MUST have been a start tag for that element name */
283 if (!p_handler || !p_handler->name || strcmp(p_handler->name, name))
285 msg_Err(p_demux, "there's no open element left for <%s>", name);
289 if (p_handler->pf_handler.smpl)
290 p_handler->pf_handler.smpl(p_input_item, p_handler->name, psz_value);
302 * \brief parses the tracklist node which only may contain <track>s
304 static bool parse_tracklist_node COMPLEX_INTERFACE
306 VLC_UNUSED(psz_element);
308 unsigned i_ntracks = 0;
311 /* now parse the <track>s */
312 while ((i_node = xml_ReaderNextNode(p_xml_reader, &name)) > 0)
314 if (i_node == XML_READER_STARTELEM)
316 if (strcmp(name, "track"))
318 msg_Err(p_demux, "unexpected child of <trackList>: <%s>",
323 /* parse the track data in a separate function */
324 if (parse_track_node(p_demux, p_input_node, p_xml_reader, "track"))
327 else if (i_node == XML_READER_ENDELEM)
331 /* the <trackList> has to be terminated */
332 if (i_node != XML_READER_ENDELEM)
334 msg_Err(p_demux, "there's a missing </trackList>");
337 if (strcmp(name, "trackList"))
339 msg_Err(p_demux, "expected: </trackList>, found: </%s>", name);
343 msg_Dbg(p_demux, "parsed %u tracks successfully", i_ntracks);
348 * \brief parse one track element
349 * \param COMPLEX_INTERFACE
351 static bool parse_track_node COMPLEX_INTERFACE
353 input_item_t *p_input_item = p_input_node->p_item;
355 char *psz_value = NULL;
356 const xml_elem_hnd_t *p_handler = NULL;
357 demux_sys_t *p_sys = p_demux->p_sys;
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 },
376 input_item_t *p_new_input = input_item_New(NULL, NULL);
379 input_item_node_t *p_new_node = input_item_node_Create(p_new_input);
381 /* reset i_track_id */
382 p_sys->i_track_id = -1;
384 while ((i_node = xml_ReaderNextNode(p_xml_reader, &name)) > 0)
387 /* element start tag */
388 case XML_READER_STARTELEM:
391 msg_Err(p_demux, "invalid XML stream");
395 p_handler = get_handler(track_elements, name);
398 msg_Err(p_demux, "unexpected element <%s>", name);
401 /* complex content is parsed in a separate function */
402 if (p_handler->cmplx)
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);
416 /* simple element content */
417 case XML_READER_TEXT:
419 psz_value = strdup(name);
420 if (unlikely(!psz_value))
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))
431 /* Make sure we have a URI */
432 char *psz_uri = input_item_GetURI(p_new_input);
434 input_item_SetURI(p_new_input, "vlc://nop");
438 if (p_sys->i_track_id < 0
439 || (unsigned)p_sys->i_track_id >= (SIZE_MAX / sizeof(p_new_input)))
441 input_item_node_AppendNode(p_input_node, p_new_node);
442 vlc_gc_decref(p_new_input);
446 if (p_sys->i_track_id >= p_sys->i_tracklist_entries)
449 pp = realloc(p_sys->pp_tracklist,
450 (p_sys->i_track_id + 1) * sizeof(*pp));
453 vlc_gc_decref(p_new_input);
454 input_item_node_Delete(p_new_node);
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;
461 else if (p_sys->pp_tracklist[p_sys->i_track_id] != NULL)
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);
469 p_sys->pp_tracklist[ p_sys->i_track_id ] = p_new_input;
470 input_item_node_Delete(p_new_node);
473 /* there MUST have been a start tag for that element name */
474 if (!p_handler || !p_handler->name || strcmp(p_handler->name, name))
476 msg_Err(p_demux, "there's no open element left for <%s>", name);
480 /* special case: location */
481 if (!strcmp(p_handler->name, "location"))
483 if (psz_value == NULL)
484 input_item_SetURI(p_new_input, "vlc://nop");
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).
493 if (p_sys->psz_base && !strstr(psz_value, "://"))
496 if (asprintf(&psz_tmp, "%s%s", p_sys->psz_base, psz_value)
501 input_item_SetURI(p_new_input, psz_tmp);
505 input_item_SetURI(p_new_input, psz_value);
506 input_item_CopyOptions(p_input_item, p_new_input);
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,
519 msg_Err(p_demux, "unexpected end of xml data");
523 input_item_node_Delete(p_new_node);
529 * \brief handles the supported <track> sub-elements
531 static bool set_item_info SIMPLE_INTERFACE
533 /* exit if setting is impossible */
534 if (!psz_name || !psz_value || !p_input)
537 /* re-convert xml special characters inside psz_value */
538 resolve_xml_special_chars(psz_value);
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"))
551 long i_num = atol(psz_value);
552 input_item_SetDuration(p_input, (mtime_t) i_num*1000);
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);
562 * \brief handles the <vlc:option> elements
564 static bool set_option SIMPLE_INTERFACE
566 /* exit if setting is impossible */
567 if (!psz_name || !psz_value || !p_input)
570 /* re-convert xml special characters inside psz_value */
571 resolve_xml_special_chars(psz_value);
573 input_item_AddOption(p_input, psz_value, 0);
579 * \brief parse the extension node of a XSPF playlist
581 static bool parse_extension_node COMPLEX_INTERFACE
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;
588 bool b_release_input_item = false;
589 const xml_elem_hnd_t *p_handler = NULL;
590 input_item_t *p_new_input = NULL;
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 },
599 /* read all extension node attributes */
600 const char *name, *value;
601 while ((name = xml_ReaderNextAttr(p_xml_reader, &value)) != NULL)
603 /* attribute: title */
604 if (!strcmp(name, "title"))
607 psz_title = strdup(value);
608 if (likely(psz_title != NULL))
609 resolve_xml_special_chars(psz_title);
611 /* extension attribute: application */
612 else if (!strcmp(name, "application"))
614 free(psz_application);
615 psz_application = strdup(value);
617 /* unknown attribute */
619 msg_Warn(p_demux, "invalid <%s> attribute:\"%s\"", psz_element,
623 /* attribute title is mandatory except for <extension> */
624 if (!strcmp(psz_element, "vlc:node"))
628 msg_Warn(p_demux, "<vlc:node> requires \"title\" attribute");
631 p_new_input = input_item_NewWithType("vlc://nop", psz_title,
633 ITEM_TYPE_DIRECTORY);
637 input_item_node_AppendItem(p_input_node, p_new_input);
638 p_input_item = p_new_input;
639 b_release_input_item = true;
643 else if (!strcmp(psz_element, "extension"))
645 if (!psz_application)
647 msg_Warn(p_demux, "<extension> requires \"application\" attribute");
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"))
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))
660 case XML_READER_STARTELEM: lvl++; break;
661 case XML_READER_ENDELEM: lvl--; break;
662 case 0: case -1: return -1;
667 free(psz_application);
670 /* parse the child elements */
671 while ((i_node = xml_ReaderNextNode(p_xml_reader, &name)) > 0)
675 /* element start tag */
676 case XML_READER_STARTELEM:
679 msg_Err(p_demux, "invalid xml stream");
681 if (b_release_input_item) vlc_gc_decref(p_new_input);
685 p_handler = get_handler(pl_elements, name);
688 msg_Err(p_demux, "unexpected element <%s>", name);
690 if (b_release_input_item) vlc_gc_decref(p_new_input);
693 /* complex content is parsed in a separate function */
694 if (p_handler->cmplx)
696 if (p_handler->pf_handler.cmplx(p_demux,
707 if (b_release_input_item) vlc_gc_decref(p_new_input);
713 case XML_READER_TEXT:
714 /* simple element content */
716 psz_value = strdup(name);
717 if (unlikely(!psz_value))
720 if (b_release_input_item) vlc_gc_decref(p_new_input);
725 /* element end tag */
726 case XML_READER_ENDELEM:
727 /* leave if the current parent node is terminated */
728 if (!strcmp(name, psz_element))
731 if (b_release_input_item) vlc_gc_decref(p_new_input);
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))
738 msg_Err(p_demux, "there's no open element left for <%s>",
741 if (b_release_input_item) vlc_gc_decref(p_new_input);
745 /* special tag <vlc:id> */
746 if (!strcmp(p_handler->name, "vlc:id"))
748 p_demux->p_sys->i_track_id = atoi(psz_value);
750 else if (p_handler->pf_handler.smpl)
752 p_handler->pf_handler.smpl(p_input_item, p_handler->name,
760 if (b_release_input_item) vlc_gc_decref(p_new_input);
765 * \brief parse the extension item node of a XSPF playlist
767 static bool parse_extitem_node COMPLEX_INTERFACE
769 VLC_UNUSED(psz_element);
770 input_item_t *p_new_input = NULL;
773 /* read all extension item attributes */
774 const char *name, *value;
775 while ((name = xml_ReaderNextAttr(p_xml_reader, &value)) != NULL)
777 /* attribute: href */
778 if (!strcmp(name, "tid"))
780 /* unknown attribute */
782 msg_Warn(p_demux, "invalid <vlc:item> attribute: \"%s\"", name);
785 /* attribute href is mandatory */
788 msg_Warn(p_demux, "<vlc:item> requires \"tid\" attribute");
792 if (i_tid >= p_demux->p_sys->i_tracklist_entries)
794 msg_Warn(p_demux, "invalid \"tid\" attribute");
798 p_new_input = p_demux->p_sys->pp_tracklist[ i_tid ];
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;
810 * \brief skips complex element content that we can't manage
812 static bool skip_element COMPLEX_INTERFACE
814 VLC_UNUSED(p_demux); VLC_UNUSED(p_input_node);
815 VLC_UNUSED(psz_element);
817 for (unsigned lvl = 1; lvl;)
818 switch (xml_ReaderNextNode(p_xml_reader, NULL))
820 case XML_READER_STARTELEM: lvl++; break;
821 case XML_READER_ENDELEM: lvl--; break;
822 case 0: case -1: return false;