141 lines
3.2 KiB
Elixir
141 lines
3.2 KiB
Elixir
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
|