defmodule FeedParser.Parser.Atom do @moduledoc """ A `FeedParser.Parser` that handles [Atom feeds](https://validator.w3.org/feed/docs/atom.html). """ alias FeedParser.XML require XML @behaviour FeedParser.Parser @impl FeedParser.Parser def accepts(data, content_type) do case content_type do "application/atom+xml" -> true _ when content_type in ["text/xml", "application/xml"] -> doc = XML.parse(data) if XML.xmlElement(doc, :name) == :feed do {true, doc} else false end _ -> false end end @impl FeedParser.Parser def parse_feed(feed) do title = text('/feed/title/text()', feed) link = attr('/feed/link/@href', feed) icon = text('/feed/icon/text()', feed) items = :xmerl_xpath.string('/feed/entry', feed) |> Enum.map(fn entry -> id = text('/entry/id/text()', entry) title = text('/entry/title/text()', entry) link = attr('/entry/link/@href', entry) updated = text('/entry/updated/text()', entry) |> Timex.parse("{ISO:Extended}") |> case do {:ok, date} -> date _ -> nil end content = text('/entry/content/text()', entry) || text('/entry/summary/text()', entry) %FeedParser.Item{ guid: id, title: title, url: link, content: content, date: updated } end) {:ok, %FeedParser.Feed{ site_url: link, title: title, image_url: icon, 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 defp attr(xpath, element) do case :xmerl_xpath.string(xpath, element) do [attr] -> XML.xmlAttribute(attr, :value) |> List.to_string() |> String.trim() _ -> nil end end end