Current section

Files

Jump to
excessibility lib html.ex
Raw

lib/html.ex

defmodule Excessibility.HTML do
@moduledoc """
Handles HTML parsing, wrapping, and static asset path prefixing.
"""
@type html_tree :: tuple() | list(tuple())
@doc """
Wraps HTML content in a complete document structure if needed.
Accepts either a binary string or a parsed Floki HTML tree.
Returns a complete HTML document as a string with proper attributes.
"""
@spec wrap(binary() | html_tree()) :: binary()
def wrap(raw_content) do
static_path = build_static_path()
wrapped =
cond do
is_binary(raw_content) ->
Floki.parse_document!(raw_content)
match?({"html", _, _}, raw_content) ->
raw_content
match?({"div", _, _}, raw_content) and contains_phx_main?(raw_content) ->
{head, html_attrs} = app_head_and_html_attrs()
{"html", html_attrs, [head, {"body", [], [raw_content]}]}
true ->
{head, html_attrs} = app_head_and_html_attrs()
{"html", html_attrs, [head, {"body", [], List.wrap(raw_content)}]}
end
wrapped
|> Floki.traverse_and_update(&prefix_paths(&1, static_path))
|> Floki.raw_html(pretty: true)
end
defp app_head_and_html_attrs do
path = Application.get_env(:excessibility, :head_render_path, "/")
conn =
:get
|> Plug.Test.conn(path)
|> app_endpoint().call([])
body = conn.resp_body
doc = Floki.parse_document!(body)
head =
case Floki.find(doc, "head") do
[h | _] -> h
_ -> {"head", [], []}
end
html_attrs =
case doc do
[{"html", attrs, _children} | _] -> attrs
_ -> []
end
{head, html_attrs}
end
defp contains_phx_main?(tree) do
case Floki.attribute(tree, "data-phx-main") do
[] -> false
_ -> true
end
end
defp build_static_path do
Path.join(Mix.Project.app_path(), "priv/static")
end
defp prefix_paths({"script", _, _}, _), do: nil
defp prefix_paths({"a", _, _} = link, _), do: link
defp prefix_paths({el, attrs, children}, static_path) do
{el, maybe_prefix_static_path(attrs, static_path), children}
end
defp prefix_paths(el, _), do: el
defp maybe_prefix_static_path(attrs, nil), do: attrs
defp maybe_prefix_static_path(attrs, static_path) do
Enum.map(attrs, fn
{"src", path} -> {"src", prefix_static_path(path, static_path)}
{"href", path} -> {"href", prefix_static_path(path, static_path)}
attr -> attr
end)
end
defp prefix_static_path("//" <> _ = url, _), do: url
defp prefix_static_path("/" <> _ = path, prefix), do: "file://" <> Path.join([prefix, path])
defp prefix_static_path(url, _), do: url
defp app_endpoint do
Application.get_env(:excessibility, :endpoint) ||
raise """
Excessibility requires a configured Phoenix endpoint.
Please set this in your config.exs:
config :excessibility, :endpoint, MyAppWeb.Endpoint
"""
end
end