lib/hologram/template/dom_tree_builder.ex

# Covered in Hologram.Template.Parser integration tests

defmodule Hologram.Template.DOMTreeBuilder do
  alias Hologram.Template.{Helpers, SyntaxError}

  def build(tags, acc \\ [])

  def build([], acc), do: acc

  def build([{:self_closing_tag, {tag_name, attrs}} | rest], acc) do
    type = Helpers.tag_type(tag_name)
    build(rest, acc ++ [{type, tag_name, attrs, []}])
  end

  def build([{:start_tag, {tag_name, attrs}} | _] = tags, acc) do
    subtree_tags = get_subtree_tags(tags)
    remaining_tags = Enum.drop(tags, Enum.count(subtree_tags))

    children_tags =
      subtree_tags
      |> Enum.drop(1)
      |> Enum.drop(-1)

    type = Helpers.tag_type(tag_name)
    children = build(children_tags)
    subtree = {type, tag_name, attrs, children}

    build(remaining_tags, acc ++ [subtree])
  end

  def build([{:text_tag, str} | rest], acc) do
    build(rest, acc ++ [{:text, str}])
  end

  defp get_subtree_tags([{:start_tag, {tag_name, _}} = start_tag | rest]) do
    {tag_buffer, num_open_tags} =
      Enum.reduce_while(rest, {[start_tag], 1}, fn tag, {tag_buffer, num_open_tags} ->
        tag_buffer = tag_buffer ++ [tag]

        num_open_tags =
          case tag do
            {:start_tag, {^tag_name, _}} ->
              num_open_tags + 1

            {:end_tag, ^tag_name} ->
              num_open_tags - 1

            _ ->
              num_open_tags
          end

        res = if num_open_tags == 0, do: :halt, else: :cont
        {res, {tag_buffer, num_open_tags}}
      end)

    if num_open_tags > 0 do
      raise SyntaxError, message: "#{tag_name} tag is unclosed"
    end

    tag_buffer
  end
end