defmodule Sneeze do alias Sneeze.Internal @doc ~s""" Render a data-structure, containing 'elements' to html. An element is either: - [tag, attribute_map | body] - [tag, attribute_map] - [tag | body] - [tag] - [:@__raw_html, html_string] - A bare, stringable value (such as a string or number) - A list of elements Examples: ``` render([:p, %{class: "outlined"}, "hello"]) render([:br]) render([[:span, "one"], [:span, "two"]]) render([:ul, %{id: "some-list"}, [:li, [:a, %{href: "/"}, "Home"]], [:li, [:a, %{href: "/about"}, "About"]]]) ``` """ def render(data) do _render(data) end defp _render(data) do case data do [] -> "" # list with tag [tag] when is_atom(tag) -> if Internal.is_void_tag?(tag) do Internal.render_void_tag(tag) else Internal.render_tag(tag) end # list with tag and attribute map [tag, attributes] when is_atom(tag) and is_map(attributes) -> if Internal.is_void_tag?(tag) do Internal.render_void_tag(tag, attributes) else Internal.render_tag(tag, attributes) end [:__@raw_html, html_string] -> html_string # list with tag, attribute map and child nodes [tag, attributes | body] when is_map(attributes) -> if Internal.is_void_tag?(tag) do Internal.render_void_tag(tag, attributes) <> _render(body) # not actually body, next elements else Internal.render_opening_tag(tag, attributes) <> _render_body(body) <> Internal.render_closing_tag(tag) end # list with tag and child nodes [tag | body] when is_atom(tag) -> if Internal.is_void_tag?(tag) do Internal.render_void_tag(tag) <> _render_body(body) # not actually body, next elements else Internal.render_opening_tag(tag) <> _render_body(body) <> Internal.render_closing_tag(tag) end # list with list of child nodes [node] when is_list(node) -> _render node # list with sub-list as first element [node | rest] when is_list(node) -> _render(node) <> _render(rest) # list with single, stringible member [node] -> to_string(node) |> HtmlEntities.encode # any non-list node bare_node -> to_string(bare_node) |> HtmlEntities.encode end end defp _render_body(body_elements) do body_elements |> Enum.map(&_render/1) |> Enum.join("") end end