RssPlaylistPlugin.cxx 3.95 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2021 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 "RssPlaylistPlugin.hxx"
21 22
#include "../PlaylistPlugin.hxx"
#include "../MemorySongEnumerator.hxx"
23
#include "tag/Builder.hxx"
24
#include "util/ASCII.hxx"
25
#include "util/StringView.hxx"
26
#include "lib/expat/ExpatParser.hxx"
27 28

/**
29
 * This is the state object for the our XML parser.
30
 */
31
struct RssParser {
32 33 34 35
	/**
	 * The list of songs (in reverse order because that's faster
	 * while adding).
	 */
36
	std::forward_list<DetachedSong> songs;
37 38 39 40 41 42

	/**
	 * The current position in the XML file.
	 */
	enum {
		ROOT, ITEM,
43
	} state{ROOT};
44 45 46 47 48 49

	/**
	 * The current tag within the "entry" element.  This is only
	 * valid if state==ITEM.  TAG_NUM_OF_ITEM_TYPES means there
	 * is no (known) tag.
	 */
50
	TagType tag_type;
51 52

	/**
53
	 * The current song URI.  It is set by the "enclosure"
54 55
	 * element.
	 */
56
	std::string location;
57

58 59
	TagBuilder tag_builder;

60
	RssParser() = default;
61 62
};

63 64 65
static void XMLCALL
rss_start_element(void *user_data, const XML_Char *element_name,
		  const XML_Char **atts)
66
{
Max Kellermann's avatar
Max Kellermann committed
67
	auto *parser = (RssParser *)user_data;
68 69

	switch (parser->state) {
70
	case RssParser::ROOT:
71
		if (StringEqualsCaseASCII(element_name, "item")) {
72
			parser->state = RssParser::ITEM;
73
			parser->location.clear();
74
			parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
75 76 77 78
		}

		break;

79
	case RssParser::ITEM:
80
		if (StringEqualsCaseASCII(element_name, "enclosure")) {
81 82
			const char *href =
				ExpatParser::GetAttributeCase(atts, "url");
83 84
			if (href != nullptr)
				parser->location = href;
85
		} else if (StringEqualsCaseASCII(element_name, "title"))
86
			parser->tag_type = TAG_TITLE;
87
		else if (StringEqualsCaseASCII(element_name, "itunes:author"))
88
			parser->tag_type = TAG_ARTIST;
89 90 91 92 93

		break;
	}
}

94 95
static void XMLCALL
rss_end_element(void *user_data, const XML_Char *element_name)
96
{
Max Kellermann's avatar
Max Kellermann committed
97
	auto *parser = (RssParser *)user_data;
98 99

	switch (parser->state) {
100
	case RssParser::ROOT:
101 102
		break;

103
	case RssParser::ITEM:
104
		if (StringEqualsCaseASCII(element_name, "item")) {
105 106 107
			if (!parser->location.empty())
				parser->songs.emplace_front(std::move(parser->location),
							    parser->tag_builder.Commit());
108

109
			parser->state = RssParser::ROOT;
110
		} else
111
			parser->tag_type = TAG_NUM_OF_ITEM_TYPES;
112 113 114 115 116

		break;
	}
}

117 118
static void XMLCALL
rss_char_data(void *user_data, const XML_Char *s, int len)
119
{
Max Kellermann's avatar
Max Kellermann committed
120
	auto *parser = (RssParser *)user_data;
121 122

	switch (parser->state) {
123
	case RssParser::ROOT:
124 125
		break;

126
	case RssParser::ITEM:
127
		if (parser->tag_type != TAG_NUM_OF_ITEM_TYPES)
128 129
			parser->tag_builder.AddItem(parser->tag_type,
						    StringView(s, len));
130 131 132 133 134 135 136 137 138 139

		break;
	}
}

/*
 * The playlist object
 *
 */

140
static std::unique_ptr<SongEnumerator>
141
rss_open_stream(InputStreamPtr &&is)
142
{
143
	RssParser parser;
144

145 146 147 148
	{
		ExpatParser expat(&parser);
		expat.SetElementHandler(rss_start_element, rss_end_element);
		expat.SetCharacterDataHandler(rss_char_data);
149
		expat.Parse(*is);
150 151
	}

152
	parser.songs.reverse();
153
	return std::make_unique<MemorySongEnumerator>(std::move(parser.songs));
154 155 156 157
}

static const char *const rss_suffixes[] = {
	"rss",
158
	nullptr
159 160 161 162
};

static const char *const rss_mime_types[] = {
	"application/rss+xml",
163
	"application/xml",
164
	"text/xml",
165
	nullptr
166 167
};

168 169 170 171
const PlaylistPlugin rss_playlist_plugin =
	PlaylistPlugin("rss", rss_open_stream)
	.WithSuffixes(rss_suffixes)
	.WithMimeTypes(rss_mime_types);