Skip to content

Commit

Permalink
Support splitting nodes while traversing (#447)
Browse files Browse the repository at this point in the history
* Support splitting nodes while traversing

* Update lib/floki/traversal.ex

* Fix formatting

---------

Co-authored-by: Philip Sampaio <[email protected]>
  • Loading branch information
martosaur and philss authored Feb 24, 2023
1 parent f3499c6 commit 9f89380
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 6 deletions.
10 changes: 5 additions & 5 deletions lib/floki.ex
Original file line number Diff line number Diff line change
Expand Up @@ -372,9 +372,9 @@ defmodule Floki do
The tree is traversed in a post-walk fashion, where the children are traversed
before the parent.
When the function `fun` encounters HTML tag, it receives a tuple with
`{name, attributes, children}`, and should either return a similar tuple or
`nil` to delete the current node.
When the function `fun` encounters HTML tag, it receives a tuple with `{name,
attributes, children}`, and should either return a similar tuple, a list of
tuples to split current node or `nil` to delete it.
The function `fun` can also encounter HTML doctype, comment or declaration and
will receive, and should return, different tuple for these types. See the
Expand Down Expand Up @@ -404,7 +404,7 @@ defmodule Floki do

@spec traverse_and_update(
html_node() | html_tree(),
(html_node() -> html_node() | nil)
(html_node() -> html_node() | [html_node()] | nil)
) :: html_node() | html_tree()

defdelegate traverse_and_update(html_tree, fun), to: Floki.Traversal
Expand Down Expand Up @@ -458,7 +458,7 @@ defmodule Floki do
html_node() | html_tree(),
traverse_acc,
(html_node(), traverse_acc ->
{html_node() | nil, traverse_acc})
{html_node() | [html_node()] | nil, traverse_acc})
) :: {html_node() | html_tree(), traverse_acc}
when traverse_acc: any()
defdelegate traverse_and_update(html_tree, acc, fun), to: Floki.Traversal
Expand Down
10 changes: 9 additions & 1 deletion lib/floki/traversal.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,15 @@ defmodule Floki.Traversal do

{mapped_head, new_acc} ->
{mapped_tail, new_acc2} = traverse_and_update(tail, new_acc, fun)
{[mapped_head | mapped_tail], new_acc2}

mapped =
if is_list(mapped_head) do
mapped_head ++ mapped_tail
else
[mapped_head | mapped_tail]
end

{mapped, new_acc2}
end
end

Expand Down
56 changes: 56 additions & 0 deletions test/floki/traversal_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,39 @@ defmodule Floki.TraversalTest do
]}
]
end

test "splits a node" do
html = [
{"div", [],
[
{"p", [], ["foo"]}
]},
{"div", [],
[
{"p", [], ["hello world"]}
]}
]

assert Floki.traverse_and_update(html, fn
{"p", attrs, [text]} ->
for word <- String.split(text) do
{"p", attrs, [word]}
end

tag ->
tag
end) == [
{"div", [],
[
{"p", [], ["foo"]}
]},
{"div", [],
[
{"p", [], ["hello"]},
{"p", [], ["world"]}
]}
]
end
end

describe "traverse_and_update/3" do
Expand Down Expand Up @@ -158,5 +191,28 @@ defmodule Floki.TraversalTest do
]}
], 5}
end

test "splits a node" do
html = [
{"p", [], ["hello world"]}
]

assert Floki.traverse_and_update(html, 0, fn
{"p", attrs, [text]}, acc ->
nodes =
for word <- String.split(text) do
{"p", attrs, [word]}
end

{nodes, acc + 1}

tag, acc ->
{tag, acc + 1}
end) ==
{[
{"p", [], ["hello"]},
{"p", [], ["world"]}
], 1}
end
end
end

0 comments on commit 9f89380

Please sign in to comment.