defmodule NLdoc.Conversion.Writer.Html.State do @moduledoc """ This module defines a struct that captures the state of the converter during a HTML conversion. """ use NLdoc.Util.State @type html_tag_or_text :: Floki.html_tag() | Floki.html_text() @type asset_to_uri_fn :: (NLdoc.Spec.Asset.t() -> String.t()) schema do field :ordered_footnote_ids, %{String.t() => number()} field :existing_footnote_ids, [String.t()] field :assets, [NLdoc.Spec.Asset.t()] field :fn_asset_to_uri, asset_to_uri_fn(), default: &NLdoc.Spec.Asset.to_base64/1 end @spec asset_to_uri(state :: t(), asset :: NLdoc.Spec.Asset.t()) :: String.t() def asset_to_uri(%__MODULE__{fn_asset_to_uri: fun}, asset = %NLdoc.Spec.Asset{}), do: fun.(asset) @doc """ Get current footnote number based on footnotes in state. ## Examples iex> %NLdoc.Conversion.Writer.Html.State{ordered_footnote_ids: %{ "abc" => 1, "xyz" => 2 }} ...> |> NLdoc.Conversion.Writer.Html.State.current_footnote_number() 2 """ @spec current_footnote_number(t()) :: number() def current_footnote_number(%__MODULE__{ordered_footnote_ids: ids}), do: map_size(ids) @doc """ Upsert new footnote to state. ## Examples When reference was encountered that refers to a valid footnote. iex> %NLdoc.Conversion.Writer.Html.State{ ...> ordered_footnote_ids: %{ "abc" => 1 }, ...> existing_footnote_ids: ["abc", "xyz"]} ...> |> NLdoc.Conversion.Writer.Html.State.upsert_footnote_id("xyz") {2, %NLdoc.Conversion.Writer.Html.State{ ordered_footnote_ids: %{"abc" => 1, "xyz" => 2}, existing_footnote_ids: ["abc", "xyz"]}} When dead reference was encountered. iex> %NLdoc.Conversion.Writer.Html.State{ ...> ordered_footnote_ids: %{ "abc" => 1 }, ...> existing_footnote_ids: ["abc", "xyz"]} ...> |> NLdoc.Conversion.Writer.Html.State.upsert_footnote_id("def") {nil, %NLdoc.Conversion.Writer.Html.State{ ordered_footnote_ids: %{"abc" => 1}, existing_footnote_ids: ["abc", "xyz"]}} When footnote is referenced that was referenced before iex> %NLdoc.Conversion.Writer.Html.State{ ...> ordered_footnote_ids: %{ "abc" => 1, "xyz" => 2 }, ...> existing_footnote_ids: ["abc", "xyz"]} ...> |> NLdoc.Conversion.Writer.Html.State.upsert_footnote_id("xyz") {2, %NLdoc.Conversion.Writer.Html.State{ ordered_footnote_ids: %{"abc" => 1, "xyz" => 2}, existing_footnote_ids: ["abc", "xyz"]}} """ @spec upsert_footnote_id(t(), String.t()) :: {number() | nil, t()} def upsert_footnote_id(state = %__MODULE__{}, id) do cond do # Footnote referenced to does not exist not Enum.member?(state.existing_footnote_ids, id) -> {nil, state} # Footnote is referenced to previously, so already has a number. Map.has_key?(state.ordered_footnote_ids, id) -> {Map.get(state.ordered_footnote_ids, id), state} true -> number = current_footnote_number(state) + 1 {number, %{state | ordered_footnote_ids: Map.put(state.ordered_footnote_ids, id, number)}} end end @doc """ Get asset by id from state. ## Examples iex> alias NLdoc.Conversion.Writer.Html.State iex> state = %State{assets: [%NLdoc.Spec.Asset{id: "123"}]} iex> state |> State.find_asset("123") %NLdoc.Spec.Asset{id: "123"} iex> state |> State.find_asset("456") nil """ @spec find_asset(state :: t(), id :: String.t()) :: NLdoc.Spec.Asset.t() | nil def find_asset(%__MODULE__{assets: assets}, id) do assets |> Enum.find(&(&1.id == id)) end end