defmodule Feedex do @moduledoc """ A simple elixir feed parser. """ require Logger alias Feedex.Helpers.{Fetch, Xml, Sanitizer} alias Feedex.Parsers.{Atom, RSS1, RSS2} @doc """ Parse feed. ## Examples iex> Feedex.parse url {:ok, feed} # {:error, :empty_url} -> Logger.warn "empty url" # {:error, :fetch_error} -> Logger.warn "fetch error" # {:error, :invalid_xml} -> Logger.warn "invalid xml" # {:error, :unknown_feed_format} -> Logger.warn "unknown feed format" # {:error, :unknown} -> Logger.error "Unknown error catched" """ @spec parse(String.t) :: String.t def parse(url) do try do with {:ok, content} <- Fetch.get(url), {:ok, xml} <- Xml.preprocess(content), {:ok, parser} <- select_parser(xml), {:ok, feed} <- parser.parse(xml) do base_url = extract_base_url(feed.url) entries = feed.entries |> Enum.filter(fn(e) -> e.title != nil && e.content != nil end) |> Enum.sort(&(DateTime.compare(&1[:updated], &2[:updated]) != :gt)) |> Enum.map(&sanitize_entry(&1, base_url)) {:ok, %{feed | entries: entries}} end rescue e -> {:error, :unknown, e} end end # -- defp select_parser(xml) do cond do Atom.valid? xml -> {:ok, Atom} RSS1.valid? xml -> {:ok, RSS1} RSS2.valid? xml -> {:ok, RSS2} true -> {:error, :unknown_feed_format} end end defp extract_base_url(url) do %{URI.parse(url) | path: "/"} |> URI.to_string end defp sanitize_entry(entry, base_url) do content = (entry.content || "") |> String.replace(~r/)<[^<]*)*<\/script>/i, "") |> Sanitizer.basic_html |> String.replace(~r/<\s*img\ssrc\s*=\s*(['"])\//i, "