defmodule HtmlAsCode do defmacro __using__(_opts) do quote do import Kernel, except: [div: 2] import HtmlAsCode end end @tags ~w(html head body title meta link style script)a ++ ~w(header footer main nav section article aside div span p)a ++ ~w(h1 h2 h3 h4 h5 h6)a ++ ~w(ul ol li dl dt dd)a ++ ~w(table thead tbody tfoot tr th td caption colgroup col)a ++ ~w(form input textarea button select option optgroup label fieldset legend)a ++ ~w(img picture source audio video track canvas iframe embed object)a ++ ~w(a em strong b i u s small sub sup mark code pre kbd samp var q blockquote cite abbr address time data)a ++ ~w(br hr wbr)a @void_tags MapSet.new(~w( area base br col embed hr img input link meta source track wbr )a) defp children_from_body({:__block__, _, exprs}), do: exprs defp children_from_body(expr), do: [expr] defp normalize([]), do: {[], []} defp normalize([[do: body]]), do: {[], children_from_body(body)} defp normalize([attrs]) when is_list(attrs), do: {attrs, []} defp normalize([child]), do: {[], [child]} defp normalize([attrs, [do: body]]), do: {attrs, children_from_body(body)} defp emit(tag, {attrs, children}) do quote bind_quoted: [ tag: tag, attrs: attrs, children: children ] do {tag, attrs, children} end end for tag <- @tags do defmacro unquote(tag)() do emit(unquote(tag), normalize([])) end defmacro unquote(tag)(arg) do emit(unquote(tag), normalize([arg])) end defmacro unquote(tag)(arg1, arg2) do emit(unquote(tag), normalize([arg1, arg2])) end end def render(node) def render({tag, attrs, children}) do if MapSet.member?(@void_tags, tag) do [ "<", Atom.to_string(tag), render_attrs(attrs), ">" ] else [ "<", Atom.to_string(tag), render_attrs(attrs), ">", Enum.map(children, &render/1), "" ] end end def render({:safe, iodata}), do: iodata def render(text) when is_binary(text), do: escape(text) def render(nodes) when is_list(nodes) do Enum.map(nodes, &render/1) end defp render_attrs([]), do: [] defp render_attrs(attrs) do attrs |> Enum.filter(fn {_k, v} -> v end) |> Enum.map(fn {k, true} -> [" ", Atom.to_string(k)] {k, v} -> [" ", Atom.to_string(k), "=\"", escape(to_string(v)), "\""] end) end defp escape(text) do text |> String.replace("&", "&") |> String.replace("<", "<") |> String.replace(">", ">") |> String.replace("\"", """) end end