defmodule Microformats2.Helpers do @spec attr_list(String.t() | [any()] | tuple(), String.t()) :: [String.t()] def attr_list(node, attr \\ "class") do node |> Floki.attribute(attr) |> List.first() |> to_string |> String.split(" ", trim: true) end @spec blank?(any()) :: boolean() def blank?(nil), do: true def blank?(""), do: true def blank?([]), do: true def blank?(_), do: false @spec present?(any()) :: boolean() def present?(v), do: not blank?(v) @spec stripped_or_nil(nil | String.t()) :: nil | String.t() def stripped_or_nil(nil), do: nil def stripped_or_nil(val), do: String.trim(val) @spec is_rootlevel?(bitstring() | tuple()) :: boolean() def is_rootlevel?(node) when is_tuple(node) do node |> attr_list("class") |> Enum.any?(&is_a?(&1, "h")) end def is_rootlevel?(class_name) when is_bitstring(class_name) do is_a?(class_name, "h") end @spec is_a?(any(), any()) :: boolean() def is_a?("h-" <> _ = type, wanted), do: wanted == "h" && valid_mf2_name?(type) def is_a?("p-" <> _ = type, wanted), do: wanted == "p" && valid_mf2_name?(type) def is_a?("e-" <> _ = type, wanted), do: wanted == "e" && valid_mf2_name?(type) def is_a?("u-" <> _ = type, wanted), do: wanted == "u" && valid_mf2_name?(type) def is_a?("dt-" <> _ = type, wanted), do: wanted == "dt" && valid_mf2_name?(type) def is_a?(_, _), do: false @spec has_a?(String.t() | [any()] | tuple(), any()) :: boolean() def has_a?(node, wanted) do node |> attr_list() |> Enum.filter(&is_a?(&1, wanted)) |> blank? end @spec abs_uri(String.t(), String.t(), any()) :: String.t() def abs_uri(url, base_url, doc) do parsed = URI.parse(url) parsed_base = URI.parse(base_url) cond do # absolute URI present?(parsed.scheme) -> url # protocol relative URI blank?(parsed.scheme) and present?(parsed.host) -> URI.to_string(%{parsed | scheme: parsed_base.scheme}) true -> base_element = Floki.find(doc, "base") new_base = if blank?(base_element) or blank?(Floki.attribute(base_element, "href")) do base_url else abs_uri(Floki.attribute(base_element, "href") |> List.first(), base_url, []) end parsed_new_base = URI.parse(new_base) new_path = Path.expand(parsed.path || "/", Path.dirname(parsed_new_base.path || "/")) URI.to_string(%{parsed | scheme: parsed_new_base.scheme, host: parsed_new_base.host, path: new_path}) end end @spec to_key(String.t()) :: String.t() def to_key(str) do if Application.get_env(:microformats2, :underscore_keys, true), do: String.replace(str, ~r/[-]/, "_"), else: str end @spec normalized_key(String.t()) :: String.t() | atom() def normalized_key(key) do if Application.get_env(:microformats2, :atomize_keys, true), do: String.to_atom(key), else: key end @spec valid_mf2_name?(String.t()) :: boolean() def valid_mf2_name?(name), do: name =~ ~r/^(?:h|p|e|u|dt)(?:-[a-z0-9]+)?(?:-[a-z]+)+$/ @spec non_h_type?(String.t()) :: boolean() def non_h_type?("p-" <> _ = type), do: valid_mf2_name?(type) def non_h_type?("u-" <> _ = type), do: valid_mf2_name?(type) def non_h_type?("dt-" <> _ = type), do: valid_mf2_name?(type) def non_h_type?("e-" <> _ = type), do: valid_mf2_name?(type) def non_h_type?(_), do: false end