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" -> case XML.parse(data) do {:error, _} -> false {:ok, doc} -> {true, doc} end _ when content_type in ["text/xml", "application/xml"] -> case XML.parse(data) do {:error, _} -> false {:ok, doc} -> if XML.xmlElement(doc, :name) == :feed do {true, doc} else false end 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) feed_author = texts('/feed/author/name/text()', feed) updated = text('/feed/updated/text()', feed) |> Timex.parse("{ISO:Extended}") |> case do {:ok, date} -> date _ -> nil end items = :xmerl_xpath.string('/feed/entry', feed) |> Enum.map(fn entry -> id = text('/entry/id/text()', entry) title = text('/entry/title/text()', entry) links = :xmerl_xpath.string('/entry/link', entry) |> Enum.map(fn link -> value = attr('/link/@href', link) rel = attr('/link/@rel', link) {value, rel} end) url = (Enum.find(links, fn {_value, rel} -> rel == "alternate" end) || List.first(links)) |> case do url when is_binary(url) -> url {url, _rel} -> url end updated = text('/entry/updated/text()', entry) |> Timex.parse("{ISO:Extended}") |> case do {:ok, date} -> date _ -> nil end author = (texts('/entry/author/name/text()', entry) || feed_author) |> case do nil -> nil authors -> Enum.join(authors, ", ") end content = text('/entry/content/text()', entry) || text('/entry/summary/text()', entry) %FeedParser.Item{ guid: id, title: title, url: url, links: links, content: content, date: updated, creator: author } end) {:ok, %FeedParser.Feed{ site_url: link, title: title, image_url: icon, last_updated: updated, items: items }} end defp text(xpath, element) do case texts(xpath, element) do [text] -> text _ -> nil end end defp texts(xpath, element) do case :xmerl_xpath.string(xpath, element) do [] -> nil els -> Enum.map(els, fn el -> XML.xmlText(el, :value) |> List.to_string() |> String.trim() end) 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