defmodule FeedParser.Parser.RSS2 do @moduledoc """ A `FeedParser.Parser` that handles [RSS 2.0 feeds](https://cyber.harvard.edu/rss/rss.html). """ alias FeedParser.XML require XML @behaviour FeedParser.Parser @impl FeedParser.Parser def accepts(data, content_type) do case content_type do "application/rss+xml" -> {true, XML.parse(data)} _ when content_type in ["text/xml", "application/xml"] -> doc = XML.parse(data) if XML.xmlElement(doc, :name) == :rss do {true, doc} else false end _ -> false end end @impl FeedParser.Parser def parse_feed(rss) do [channel] = :xmerl_xpath.string('/rss/channel', rss) title = text('/channel/title/text()', channel) link = text('/channel/link/text()', channel) image = text('/channel/image/url/text()', channel) items = :xmerl_xpath.string('/channel/item', channel) |> Enum.map(fn item -> guid = text('/item/guid/text()', item) title = text('/item/title/text()', item) link = text('/item/link/text()', item) description = text('/item/description/text()', item) pubDate = text('/item/pubDate/text()', item) |> Timex.parse("{RFC1123}") |> case do {:ok, date} -> date _ -> nil end %FeedParser.Item{ guid: guid, title: title, url: link, content: description, date: pubDate } end) {:ok, %FeedParser.Feed{ site_url: link, title: title, image_url: image, items: items }} end defp text(xpath, element) do case :xmerl_xpath.string(xpath, element) do [el] -> XML.xmlText(el, :value) |> List.to_string() |> String.trim() _ -> nil end end end