defmodule Gulib.LinkerUtil do alias Gulib.HTMLParse @auto_link_schemas ~w(mailto http https thunder ftp sftp svn) def auto_link_schemas, do: @auto_link_schemas @doc """ 返回html中链接的个数 """ def links_amount(html) do html |> HTMLParse.parse |> HTMLParse.find("a") |> length end def append_rest(rest, extend_rest) do rest |> List.insert_at(-1, extend_rest) end @doc """ 自动生成链接 ## Params ``` # opts * html: 生成链接相关参数 * target # _blank or nil (nil) * sanitize:是否过滤标签 true/false 默认为false * sanitize_options: 过滤标签的参数 * jump_to # 用于跳转的基础url * jump_fn # 调用的时候可以用此回调 * raw_html # true/false # 默认为true * html_escape # true/false # 默认为false * normalize_rest_fn # 格式化链接内容的方法 * normalize_href_fn # 格式化链接链接的方法 * normalize_linker_fn # 格式化链接, 可以灵活的转换链接! ``` """ def auto_link(html_or_tree, opts \\ []) do html_opts = opts |> Gulib.get(:html, %{}) html_escape = opts |> Gulib.get(:html_escape, false) opts = opts |> Keyword.take([ :raw_html, :jump_to, :jump_fn, :normalize_rest_fn, :normalize_href_fn, :html_safe, :normalize_linker_fn ]) |> Keyword.merge( html_escape: html_escape, html: html_opts ) rebuild_html_tree = html_or_tree |> HTMLParse.parse |> HTMLParse.transform(fn(html_node, extends) -> impl_transform(html_node, extends, opts) end) raw_html = rebuild_html_tree |> HTMLParse.raw_html cond do Keyword.get(opts, :html_safe, false) -> {:safe, raw_html} Keyword.get(opts, :raw_html, true) -> raw_html true -> raw_html |> HTMLParse.parse end end defp impl_transform_linker({"a", attrs, rest}, extends, opts) do if is_function(normalize_linker_fn = Gulib.get(opts, :normalize_linker_fn, nil)) do normalize_linker_fn.( [ attrs: attrs, rest: rest, extends: extends, opts: opts ] ) else {"a", attrs, rest} end end # 对A标签的处理 defp impl_transform({"a", attrs, rest}, extends, opts) do {"a", impl_normalize_linker_attrs(attrs, opts), impl_normalize_linker_rest(rest, opts)} |> impl_transform_linker(extends, opts) end defp impl_transform(plain_text, extends, opts) when is_binary(plain_text) do impl_transform_plain_text(plain_text, extends, opts) end defp impl_transform(html_tree, _extends, _opts), do: html_tree defp impl_transform_plain_text(plain_text, extends, opts) do %{ ancestors: ancestors } = extends plain_text = if Gulib.get(opts, :html_escape, false) do Gulib.html_escape(plain_text) else plain_text end if Enum.member?(ancestors, "a") do plain_text else Gulib.Const.scan_url_regex |> Regex.replace(plain_text, fn(link_url, _) -> { "a", impl_normalize_linker_attrs([{"href", impl_normalize_href(link_url, opts)}], opts), impl_normalize_linker_rest([link_url], opts) } |> impl_transform_linker(extends, opts) |> HTMLParse.raw_html end) end end # 对标签属性的处理 # opts: # * target defp impl_normalize_linker_attrs(attrs, opts) do parsed_attrs = HTMLParse.parse_attrs(attrs) html_opts = opts |> Keyword.get(:html, %{}) target = html_opts |> Map.get(:target, nil) parsed_attrs = if Enum.member?(["_blank", "_parent", "_self"], target) do parsed_attrs |> Keyword.put(:target, target) else parsed_attrs |> Keyword.drop([:target]) end parsed_attrs |> HTMLParse.flatten_attrs end # 实现格式化rest defp impl_normalize_linker_rest(rest, opts) do normalize_rest_fn = opts |> Keyword.get(:normalize_rest_fn, nil) if is_function(normalize_rest_fn) do normalize_rest_fn.(rest) else rest end end def normalize_url(url) do url = cond do Gulib.blank?(url) -> "" String.starts_with?(url, ["/"]) -> url true -> header_str = url |> String.trim |> String.split([":"]) |> List.first |> String.downcase cond do Enum.member?(auto_link_schemas(), header_str) -> url Regex.match?(Gulib.Const.match_url_regex, url) -> "http://" <> url true -> "/#{url}" end end url |> Gulib.html_unescape end # 实现格式化href defp impl_normalize_href(url, opts) do normalize_href_fn = opts |> Keyword.get(:normalize_href_fn, nil) if is_function(normalize_href_fn) do url |> normalize_href_fn.() |> normalize_url else url = normalize_url(url) jump_to_base_uri = Keyword.get(opts, :jump_to, nil) jump_fn = Keyword.get(opts, :jump_fn, nil) cond do is_function(jump_fn) -> jump_fn.(url) Gulib.present?(jump_to_base_uri) -> jump_to_base_uri <> URI.encode_www_form(url) true -> url end end end end