XspfPlaylistPlugin.cxx 5.03 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
 * http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

20
#include "config.h"
21
#include "XspfPlaylistPlugin.hxx"
22 23
#include "../PlaylistPlugin.hxx"
#include "../MemorySongEnumerator.hxx"
24
#include "DetachedSong.hxx"
Max Kellermann's avatar
Max Kellermann committed
25
#include "input/InputStream.hxx"
26
#include "tag/Builder.hxx"
27
#include "util/StringView.hxx"
28
#include "lib/expat/ExpatParser.hxx"
29
#include "Log.hxx"
30 31 32 33

#include <string.h>

/**
34
 * This is the state object for our XML parser.
35
 */
36
struct XspfParser {
37 38 39 40
	/**
	 * The list of songs (in reverse order because that's faster
	 * while adding).
	 */
41
	std::forward_list<DetachedSong> songs;
42 43 44 45 46 47 48 49 50 51 52 53 54 55

	/**
	 * The current position in the XML file.
	 */
	enum {
		ROOT, PLAYLIST, TRACKLIST, TRACK,
		LOCATION,
	} state;

	/**
	 * The current tag within the "track" element.  This is only
	 * valid if state==TRACK.  TAG_NUM_OF_ITEM_TYPES means there
	 * is no (known) tag.
	 */
56
	TagType tag_type;
57 58

	/**
59
	 * The current song URI.  It is set by the "location" element.
60
	 */
61
	std::string location;
62

63 64
	TagBuilder tag_builder;

65
	XspfParser()
66
		:state(ROOT) {}
67 68
};

69 70 71
static void XMLCALL
xspf_start_element(void *user_data, const XML_Char *element_name,
		   gcc_unused const XML_Char **atts)
72
{
73
	XspfParser *parser = (XspfParser *)user_data;
74 75

	switch (parser->state) {
76
	case XspfParser::ROOT:
77
		if (strcmp(element_name, "playlist") == 0)
78
			parser->state = XspfParser::PLAYLIST;
79 80 81

		break;

82
	case XspfParser::PLAYLIST:
83
		if (strcmp(element_name, "trackList") == 0)
84
			parser->state = XspfParser::TRACKLIST;
85 86 87

		break;

88
	case XspfParser::TRACKLIST:
89
		if (strcmp(element_name, "track") == 0) {
90
			parser->state = XspfParser::TRACK;
91
			parser->location.clear();
92
			parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
93 94 95 96
		}

		break;

97
	case XspfParser::TRACK:
98
		if (strcmp(element_name, "location") == 0)
99
			parser->state = XspfParser::LOCATION;
100
		else if (strcmp(element_name, "title") == 0)
101
			parser->tag_type = TAG_TITLE;
102 103 104
		else if (strcmp(element_name, "creator") == 0)
			/* TAG_COMPOSER would be more correct
			   according to the XSPF spec */
105
			parser->tag_type = TAG_ARTIST;
106
		else if (strcmp(element_name, "annotation") == 0)
107
			parser->tag_type = TAG_COMMENT;
108
		else if (strcmp(element_name, "album") == 0)
109
			parser->tag_type = TAG_ALBUM;
110
		else if (strcmp(element_name, "trackNum") == 0)
111
			parser->tag_type = TAG_TRACK;
112 113 114

		break;

115
	case XspfParser::LOCATION:
116 117 118 119
		break;
	}
}

120 121
static void XMLCALL
xspf_end_element(void *user_data, const XML_Char *element_name)
122
{
123
	XspfParser *parser = (XspfParser *)user_data;
124 125

	switch (parser->state) {
126
	case XspfParser::ROOT:
127 128
		break;

129
	case XspfParser::PLAYLIST:
130
		if (strcmp(element_name, "playlist") == 0)
131
			parser->state = XspfParser::ROOT;
132 133 134

		break;

135
	case XspfParser::TRACKLIST:
136
		if (strcmp(element_name, "tracklist") == 0)
137
			parser->state = XspfParser::PLAYLIST;
138 139 140

		break;

141
	case XspfParser::TRACK:
142
		if (strcmp(element_name, "track") == 0) {
143 144 145
			if (!parser->location.empty())
				parser->songs.emplace_front(std::move(parser->location),
							    parser->tag_builder.Commit());
146

147
			parser->state = XspfParser::TRACKLIST;
148
		} else
149
			parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
150 151 152

		break;

153 154
	case XspfParser::LOCATION:
		parser->state = XspfParser::TRACK;
155 156 157 158
		break;
	}
}

159 160
static void XMLCALL
xspf_char_data(void *user_data, const XML_Char *s, int len)
161
{
162
	XspfParser *parser = (XspfParser *)user_data;
163 164

	switch (parser->state) {
165 166 167
	case XspfParser::ROOT:
	case XspfParser::PLAYLIST:
	case XspfParser::TRACKLIST:
168 169
		break;

170
	case XspfParser::TRACK:
171
		if (!parser->location.empty() &&
172
		    parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
173 174
			parser->tag_builder.AddItem(parser->tag_type,
						    StringView(s, len));
175 176 177

		break;

178
	case XspfParser::LOCATION:
179
		parser->location.assign(s, len);
180 181 182 183 184 185 186 187 188 189

		break;
	}
}

/*
 * The playlist object
 *
 */

190
static SongEnumerator *
191
xspf_open_stream(InputStreamPtr &&is)
192
{
193
	XspfParser parser;
194

195 196 197 198
	{
		ExpatParser expat(&parser);
		expat.SetElementHandler(xspf_start_element, xspf_end_element);
		expat.SetCharacterDataHandler(xspf_char_data);
199
		expat.Parse(*is);
200 201
	}

202
	parser.songs.reverse();
203
	return new MemorySongEnumerator(std::move(parser.songs));
204 205 206 207
}

static const char *const xspf_suffixes[] = {
	"xspf",
208
	nullptr
209 210 211 212
};

static const char *const xspf_mime_types[] = {
	"application/xspf+xml",
213
	nullptr
214 215 216
};

const struct playlist_plugin xspf_playlist_plugin = {
217
	"xspf",
218

219 220 221 222
	nullptr,
	nullptr,
	nullptr,
	xspf_open_stream,
223

224 225 226
	nullptr,
	xspf_suffixes,
	xspf_mime_types,
227
};