lib/icon/parse.ex

defmodule Icon.Parse do
  @moduledoc """
  Icon parsing module.

  """

  defmodule Icon do
    @moduledoc "Icon struct"

    @type t :: %__MODULE__{href: String.t(), rel: String.t(), type: String.t(), sizes: String.t()}

    defstruct [:href, :rel, :type, :sizes]
  end

  @doc """
  Parse a html string and extract all the icons from it

  ## Parameters

    - html: String
    - url: a URL string

  ## Returns

    - `[Icon.Parse.Icon.t()]`
    - `[]` if no icon discovered

  ## Examples

      iex> Icon.Parse.parse(-s(<html><head><link rel="icon" href="a/favicon.ico" /></head></html>), "http://test.com")
      [%Icon.Parse.Icon{href: "http://test.com/a/favicon.ico", rel: "icon"}]

  """
  @spec parse(String.t(), String.t()) :: [Icon.t()] | []
  def parse(html, url) do
    html
    |> extract_icons
    |> Enum.map(fn icon -> resolve_href(icon, url) end)
  end

  defp extract_icons(html) do
    html
    |> Floki.find("link")
    |> Enum.filter(fn {_tag, attrs, _children} -> icon_link?(attrs) end)
    |> Enum.map(fn {_tag, attrs, _children} -> attrs end)
    |> Enum.map(fn attrs -> make_icon(attrs) end)
  end

  defp icon_link?(attributes) do
    Enum.any?(attributes, fn {att, value} -> att == "rel" and String.contains?(value, "icon") end)
  end

  defp attr([{att, value} | tl], name) do
    if att == name do
      value
    else
      attr(tl, name)
    end
  end

  defp attr([], _name) do
    nil
  end

  defp make_icon(attrs) do
    %Icon{
      href: attr(attrs, "href"),
      sizes: attr(attrs, "sizes"),
      rel: attr(attrs, "rel"),
      type: attr(attrs, "type")
    }
  end

  defp resolve_href(icon, url) do
    href = icon.href

    case Regex.match?(~r/^https?/, href) do
      true ->
        icon

      false ->
        uri = URI.parse(href)

        case uri do
          %URI{host: nil} ->
            %{icon | href: %{URI.parse(url) | path: uri.path} |> URI.to_string()}

          %URI{scheme: nil} ->
            %{icon | href: %{uri | scheme: "http"} |> URI.to_string()}

          _ ->
            icon
        end
    end
  end
end