Tree traversal and manipulation functions for Quillon documents.
A path is a list of integers representing indices into children arrays:
# Path [] = root node
# Path [0] = first child
# Path [0, 1] = second child of first childExamples
doc = Quillon.document([
Quillon.paragraph("Hello"),
Quillon.heading(1, "Title")
])
# Get node at path
{:ok, para} = Quillon.Path.get(doc, [0])
# Update node at path
{:ok, updated} = Quillon.Path.update(doc, [0], fn para ->
put_elem(para, 1, Map.put(elem(para, 1), :custom, true))
end)
# Delete node at path
{:ok, smaller} = Quillon.Path.delete(doc, [1])
Summary
Types
A Quillon document node tuple
A path is a list of non-negative integers representing child indices
Functions
Delete the node at the given path.
Find the path to a node with the given id.
Get the node at the given path.
Get a node by its id.
Insert a node at the given path.
Move a node from one path to another.
Reorder children at the given path according to an id list.
Update the node at the given path using a function.
Update a node by its id using a function.
Types
A Quillon document node tuple
@type path() :: [non_neg_integer()]
A path is a list of non-negative integers representing child indices
Functions
Delete the node at the given path.
Returns {:ok, updated_root} if successful, {:error, :invalid_path} if the path doesn't exist.
Note: Cannot delete the root node (empty path).
Examples
iex> doc = {:document, %{}, [{:paragraph, %{}, []}, {:heading, %{level: 1}, []}]}
iex> {:ok, updated} = Quillon.Path.delete(doc, [0])
iex> length(elem(updated, 2))
1
iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.delete(doc, [])
{:error, :invalid_path}
iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.delete(doc, [5])
{:error, :invalid_path}
Find the path to a node with the given id.
Searches depth-first for a node whose attrs contain the specified id.
Returns {:ok, path} if found, {:error, :not_found} if no node has that id.
Examples
iex> doc = {:document, %{}, [{:paragraph, %{id: "p1"}, []}, {:paragraph, %{id: "p2"}, []}]}
iex> Quillon.Path.find_path(doc, "p1")
{:ok, [0]}
iex> doc = {:document, %{}, [{:paragraph, %{id: "p1"}, []}, {:paragraph, %{id: "p2"}, []}]}
iex> Quillon.Path.find_path(doc, "p2")
{:ok, [1]}
iex> doc = {:document, %{id: "doc"}, [{:paragraph, %{id: "p1"}, []}]}
iex> Quillon.Path.find_path(doc, "doc")
{:ok, []}
iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.find_path(doc, "missing")
{:error, :not_found}
Get the node at the given path.
Returns {:ok, node} if found, {:error, :invalid_path} if the path doesn't exist.
Examples
iex> doc = {:document, %{}, [{:paragraph, %{}, [{:text, %{text: "Hi", marks: []}, []}]}]}
iex> Quillon.Path.get(doc, [])
{:ok, {:document, %{}, [{:paragraph, %{}, [{:text, %{text: "Hi", marks: []}, []}]}]}}
iex> doc = {:document, %{}, [{:paragraph, %{}, [{:text, %{text: "Hi", marks: []}, []}]}]}
iex> Quillon.Path.get(doc, [0])
{:ok, {:paragraph, %{}, [{:text, %{text: "Hi", marks: []}, []}]}}
iex> doc = {:document, %{}, [{:paragraph, %{}, [{:text, %{text: "Hi", marks: []}, []}]}]}
iex> Quillon.Path.get(doc, [0, 0])
{:ok, {:text, %{text: "Hi", marks: []}, []}}
iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.get(doc, [5])
{:error, :invalid_path}
Get a node by its id.
Returns {:ok, node} if found, {:error, :not_found} if no node has that id.
Examples
iex> doc = {:document, %{}, [{:paragraph, %{id: "p1"}, []}, {:paragraph, %{id: "p2"}, []}]}
iex> Quillon.Path.get_by_id(doc, "p1")
{:ok, {:paragraph, %{id: "p1"}, []}}
iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.get_by_id(doc, "missing")
{:error, :not_found}
Insert a node at the given path.
The path specifies where to insert: the last element is the index position in the parent's children list. The node is inserted at that index, shifting existing nodes to the right.
Returns {:ok, updated_root} if successful, {:error, :invalid_path} if the parent path doesn't exist.
Examples
iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> new_para = {:paragraph, %{id: "new"}, []}
iex> {:ok, updated} = Quillon.Path.insert(doc, [1], new_para)
iex> length(elem(updated, 2))
2
iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> new_para = {:paragraph, %{}, []}
iex> {:ok, updated} = Quillon.Path.insert(doc, [0], new_para)
iex> {:ok, first} = Quillon.Path.get(updated, [0])
iex> elem(first, 1)
%{}
Move a node from one path to another.
The node at from_path is removed and inserted at to_path.
The to_path is calculated after the removal.
Returns {:ok, updated_root} if successful, or an error tuple.
Examples
iex> doc = {:document, %{}, [{:paragraph, %{id: "p1"}, []}, {:paragraph, %{id: "p2"}, []}, {:paragraph, %{id: "p3"}, []}]}
iex> {:ok, updated} = Quillon.Path.move(doc, [0], [2])
iex> {:ok, first} = Quillon.Path.get(updated, [0])
iex> Map.get(elem(first, 1), :id)
"p2"
iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.move(doc, [5], [0])
{:error, :invalid_path}
Reorder children at the given path according to an id list.
The children of the node at path are reordered to match the order of ids
in id_list. Children whose ids are not in the list are appended at the end
in their original order.
Returns {:ok, updated_root} if successful, or an error tuple.
Examples
iex> para1 = {:paragraph, %{id: "p1"}, []}
iex> para2 = {:paragraph, %{id: "p2"}, []}
iex> para3 = {:paragraph, %{id: "p3"}, []}
iex> doc = {:document, %{}, [para1, para2, para3]}
iex> {:ok, updated} = Quillon.Path.reorder(doc, [], ["p3", "p1", "p2"])
iex> Enum.map(elem(updated, 2), fn {_, attrs, _} -> attrs.id end)
["p3", "p1", "p2"]
iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.reorder(doc, [5], ["a"])
{:error, :invalid_path}
@spec update(doc_node(), path(), (doc_node() -> doc_node())) :: {:ok, doc_node()} | {:error, :invalid_path}
Update the node at the given path using a function.
Returns {:ok, updated_root} if successful, {:error, :invalid_path} if the path doesn't exist.
Examples
iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> {:ok, updated} = Quillon.Path.update(doc, [0], fn {type, attrs, children} -> {type, Map.put(attrs, :custom, true), children} end)
iex> {:ok, para} = Quillon.Path.get(updated, [0])
iex> elem(para, 1)
%{custom: true}
iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.update(doc, [5], fn n -> n end)
{:error, :invalid_path}
@spec update_by_id(doc_node(), any(), (doc_node() -> doc_node())) :: {:ok, doc_node()} | {:error, :not_found}
Update a node by its id using a function.
Returns {:ok, updated_root} if successful, {:error, :not_found} if no node has that id.
Examples
iex> doc = {:document, %{}, [{:paragraph, %{id: "p1"}, []}]}
iex> {:ok, updated} = Quillon.Path.update_by_id(doc, "p1", fn {type, attrs, children} -> {type, Map.put(attrs, :custom, true), children} end)
iex> {:ok, para} = Quillon.Path.get_by_id(updated, "p1")
iex> Map.get(elem(para, 1), :custom)
true
iex> doc = {:document, %{}, [{:paragraph, %{}, []}]}
iex> Quillon.Path.update_by_id(doc, "missing", fn n -> n end)
{:error, :not_found}