Current section
Files
Jump to
Current section
Files
lib/simple_form/element.ex
defmodule SimpleForm.Element do
@moduledoc """
SimpleForm.Element implements functions for manipulating XML elements,
such as finding one by name in a list of elements or in the children of another element.
"""
@doc """
Get the name of an element.
## Examples
iex> SimpleForm.Element.name({"w:p", [], []})
"w:p"
iex> SimpleForm.Element.name(nil)
nil
"""
@spec name(nil) :: nil
@spec name(SimpleForm.element()) :: String.t()
def name(nil), do: nil
def name({name, _, _}), do: name
@doc """
Get the attributes of an element.
## Examples
iex> SimpleForm.Element.attrs(nil)
nil
iex> SimpleForm.Element.attrs({"w:p", [], ["some text"]})
[]
iex> SimpleForm.Element.attrs({"w:p", [{"id", "1"}], ["some text"]})
[{"id", "1"}]
"""
@spec attrs(nil) :: nil
@spec attrs(SimpleForm.element()) :: SimpleForm.attribute()
def attrs(nil), do: nil
def attrs({_, attrs, _}), do: attrs
@doc """
Get the children of an element.
## Examples
iex> SimpleForm.Element.children(nil)
nil
iex> SimpleForm.Element.children({"w:p", [], []})
[]
iex> SimpleForm.Element.children({"w:p", [], ["some text"]})
["some text"]
iex> SimpleForm.Element.children({"w:p", [{"id", "1"}], ["some text", "more text"]})
["some text", "more text"]
"""
def children(nil), do: nil
def children({_, _, children}), do: children
@doc """
Recursively get the text content of an element or list of elements.
## Examples
iex> SimpleForm.Element.text(nil)
nil
iex> SimpleForm.Element.text("some text")
"some text"
iex> SimpleForm.Element.text({"w:p", [], []})
""
iex> SimpleForm.Element.text([{"w:p", [], []}, {"w:tbl", [], []}])
""
iex> SimpleForm.Element.text({"w:p", [], ["some text"]})
"some text"
iex> SimpleForm.Element.text([{"w:p", [], ["some text", " other text."]}, {"w:tbl", [], ["more text"]}, {"w:tbl", [], []}])
"some text other text.more text"
iex> SimpleForm.Element.text([{"w:p", [], ["some text", " other text.", " "]}, {"w:tbl", [], ["more text"]}, {"w:tbl", [], []}])
"some text other text. more text"
"""
@spec text(nil) :: nil
@spec text(String.t()) :: String.t()
@spec text(SimpleForm.element() | [SimpleForm.element()]) :: String.t()
def text(nil), do: nil
def text(text) when is_binary(text), do: text
def text({_, _, children}), do: text(children)
def text([]), do: ""
def text([head | tail]), do: text(head) <> text(tail)
@doc """
Recursively find the first element to match the given predicate, or return the given default value.
This function performs a depth-first search through the children of an element or list of elements,
and returns the first element for which the predicate returns true.
## Examples
iex> SimpleForm.Element.find_recursive(
...> [{"w:p", [], []}, {"w:r", [], ["some text"]}],
...> fn element -> element |> SimpleForm.Element.name() === "w:r" end
...> )
{"w:r", [], ["some text"]}
iex> SimpleForm.Element.find_recursive(
...> [{"w:p", [], [{"w:r", [{"id", "1"}], ["text"]}]}, {"w:r", [{"id", "2"}], ["text"]}],
...> fn element -> element |> SimpleForm.Element.name() === "w:r" end
...> )
{"w:r", [{"id", "1"}], ["text"]}
iex> SimpleForm.Element.find_recursive([], fn _ -> true end)
nil
iex> SimpleForm.Element.find_recursive([], fn _ -> true end, "test")
"test"
iex> SimpleForm.Element.find_recursive([{"w:p", [], ["text"]}], fn _ -> false end)
nil
iex> SimpleForm.Element.find_recursive([{"w:p", [], ["text"]}], fn _ -> false end, "default")
"default"
"""
@spec find_recursive(element | [element], (element -> boolean())) :: element | nil
when element: SimpleForm.element()
def find_recursive([], _predicate), do: nil
def find_recursive(content, _predicate) when is_binary(content), do: nil
def find_recursive([head | tail], predicate) when is_list(tail),
do: find_recursive(head, predicate) || find_recursive(tail, predicate)
def find_recursive(element = {_, _, children}, predicate),
do: if(predicate.(element), do: element, else: find_recursive(children, predicate))
@spec find_recursive([element], (element -> boolean()), default) :: element | default
when element: SimpleForm.element(), default: var
def find_recursive(elements, predicate, default),
do: find_recursive(elements, predicate) || default
@doc """
Find an element by name in a list of elements.
## Options
* `recursive` (boolean): If true, the function will perform a recursive, depth-first search through the children of each element in the list.
## Examples
By default, `SimpleForm.Element.find_by_name/4` only searches the list's elements.
iex> SimpleForm.Element.find_by_name([{"w:p", [], []}, {"w:tbl", [], []}], "w:tbl")
{"w:tbl", [], []}
iex> SimpleForm.Element.find_by_name([{"w:p", [], []}, {"w:tbl", [], []}], "w:sdt")
nil
iex> SimpleForm.Element.find_by_name([{"w:p", [], []}, {"w:tbl", [], []}], "w:sdt", {"w:sdt", [], []})
{"w:sdt", [], []}
For a recursive, depth-first search through the children of each element in the list, set the `recursive` option to `true`.
iex> SimpleForm.Element.find_by_name([{"w:p", [], [{"w:r", [], ["some text"]}]}], "w:r", nil, recursive: true)
{"w:r", [], ["some text"]}
iex> SimpleForm.Element.find_by_name([{"w:p", [], [{"w:r", [], ["some text"]}]}], "w:sdt", nil, recursive: true)
nil
iex> SimpleForm.Element.find_by_name([{"w:p", [], [{"w:r", [], ["some text"]}, " ", {"w:sdt", [], []}]}], "w:sdt", nil, recursive: true)
{"w:sdt", [], []}
iex> SimpleForm.Element.find_by_name([{"w:p", [], [{"w:r", [], ["first text"]}]}, " ", {"w:p", [], [{"w:r", [], ["second text"]}]}], "w:r", nil, recursive: true)
{"w:r", [], ["first text"]}
Note: the option `recursive: true` requires setting a default value explicitly, otherwise this option is assumed as the default value.
iex> SimpleForm.Element.find_by_name([{"w:p", [], [{"w:r", [], ["some text"]}]}], "w:r", recursive: true)
[recursive: true]
iex> SimpleForm.Element.find_by_name([{"w:p", [], [{"w:r", [], ["some text"]}]}], "w:r", recursive: false)
[recursive: false]
"""
@spec find_by_name(
elements :: [SimpleForm.element()],
name :: String.t(),
default :: default,
opts :: [recursive: boolean()] | nil
) :: SimpleForm.element() | default
when default: var
def find_by_name(elements, name, default \\ nil, opts \\ [recursive: false])
def find_by_name(elements, name, default, recursive: false)
when is_list(elements) and is_binary(name) do
List.keyfind(elements, name, 0, default)
end
def find_by_name(elements, name, default, recursive: true)
when is_list(elements) and is_binary(name) do
elements
|> find_recursive(
fn element -> SimpleForm.element?(element) and name(element) === name end,
default
)
end
@doc """
Find multiple elements by their names in a list of elements.
The order and placement of the elements in the list is preserved w.r.t. the given names.
This function is more performant than calling `find_by_name/3` multiple times,
as it only iterates over `elements` once.
## Examples
iex> elements = [{"w:p", [], [{"w:r", [], ["some text"]}]}, {"w:tbl", [], []}]
iex> SimpleForm.Element.find_many_by_name(elements, ["w:p"])
[{"w:p", [], [{"w:r", [], ["some text"]}]}]
iex> SimpleForm.Element.find_many_by_name(elements, ["w:r"])
[nil]
iex> SimpleForm.Element.find_many_by_name(elements, ["w:tbl", "w:p"])
[{"w:tbl", [], []}, {"w:p", [], [{"w:r", [], ["some text"]}]}]
iex> SimpleForm.Element.find_many_by_name(elements, ["w:tbl", "w:r"])
[{"w:tbl", [], []}, nil]
iex> SimpleForm.Element.find_many_by_name(elements, ["w:sdt", "w:p"])
[nil, {"w:p", [], [{"w:r", [], ["some text"]}]}]
"""
@spec find_many_by_name(
elements :: [SimpleForm.element()],
names :: [String.t()]
) :: [SimpleForm.element() | nil]
def find_many_by_name(elements, names) when is_list(elements) and is_list(names) do
elements_map =
elements
|> Enum.filter(&SimpleForm.element?/1)
|> Map.new(fn element -> {name(element), element} end)
names |> Enum.map(&Map.get(elements_map, &1))
end
end