Packages
floki
0.6.0
0.38.4
0.38.3
0.38.2
0.38.1
0.38.0
0.37.1
0.37.0
0.36.3
0.36.2
0.36.1
0.36.0
0.35.4
0.35.3
0.35.2
0.35.1
0.35.0
0.34.3
0.34.2
0.34.1
0.34.0
0.33.1
0.33.0
0.32.1
0.32.0
0.31.0
0.30.1
0.30.0
0.29.0
0.28.0
0.27.0
0.26.0
0.25.0
0.24.0
0.23.1
0.23.0
0.22.0
0.21.0
0.20.4
0.20.3
0.20.2
0.20.1
0.20.0
0.19.3
0.19.2
0.19.1
0.19.0
0.18.1
0.18.0
0.17.2
0.17.1
0.17.0
0.16.0
0.15.0
0.14.0
0.13.2
0.13.1
0.13.0
0.12.1
0.12.0
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.6.1
0.6.0
0.5.0
0.4.1
0.4.0
0.3.3
0.3.2
0.3.1
0.3.0
0.2.1
0.2.0
0.1.1
0.1.0
0.0.5
0.0.4
0.0.3
0.0.2
0.0.1
Floki is a simple HTML parser that enables search for nodes using CSS selectors.
Current section
Files
Jump to
Current section
Files
lib/floki/finder.ex
defmodule Floki.Finder do
@moduledoc """
The finder engine traverse the HTML tree searching for nodes matching
selectors.
"""
alias Floki.Selector
alias Floki.SelectorParser
alias Floki.SelectorTokenizer
@type html_tree :: tuple | list
@doc """
Find elements inside a HTML tree.
"""
@spec find(html_tree, binary) :: html_tree
def find(html_tree, selector_as_string) do
selectors = get_selectors(selector_as_string)
html_tree
|> traverse([], selectors, [])
|> Enum.reverse
end
defp get_selectors(selector_as_string) do
Enum.map String.split(selector_as_string, ","), fn(s) ->
SelectorTokenizer.tokenize(s) |> SelectorParser.parse
end
end
defp traverse(_, _, [], acc), do: acc
defp traverse({}, _, _, acc), do: acc
defp traverse([], _, _, acc), do: acc
defp traverse(string, _, _, acc) when is_binary(string), do: acc
defp traverse({:comment, _comment},_, _, acc), do: acc
defp traverse({:pi, _xml, _xml_attrs},_, _, acc), do: acc
defp traverse([node|sibling_nodes], _, selectors, acc) do
acc = traverse(node, sibling_nodes, selectors, acc)
traverse(sibling_nodes, [], selectors, acc)
end
defp traverse(node, sibling_nodes, [head_selector|tail_selectors], acc) do
acc = traverse(node, sibling_nodes, head_selector, acc)
traverse(node, sibling_nodes, tail_selectors, acc)
end
defp traverse({_, _, children_nodes} = node, sibling_nodes, selector, acc) do
acc =
if Selector.match?(node, selector) do
combinator = selector.combinator
case combinator do
nil -> [node|acc]
_ ->
case combinator.match_type do
:descendant ->
traverse(children_nodes, sibling_nodes, combinator.selector, acc)
:child ->
traverse_child(children_nodes, sibling_nodes, combinator.selector, acc)
:sibling ->
traverse_sibling(children_nodes, sibling_nodes, combinator.selector, acc)
:general_sibling ->
traverse_general_sibling(children_nodes, sibling_nodes, combinator.selector, acc)
other ->
raise "Combinator of type \"#{other}\" not implemented"
end
end
else
acc
end
traverse(children_nodes, sibling_nodes, selector, acc)
end
defp traverse_child(nodes, sibling_nodes, selector, acc) do
Enum.reduce(nodes, acc, fn(n, res_acc) ->
if Selector.match?(n, selector) do
case selector.combinator do
nil -> [n|res_acc]
_ ->
{_, _, children_nodes} = n
traverse(children_nodes, sibling_nodes, selector.combinator.selector, res_acc)
end
else
res_acc
end
end)
end
defp traverse_sibling(_nodes, sibling_nodes, selector, acc) do
sibling_node = Enum.drop_while(sibling_nodes, &ignore_node?/1) |> hd
if Selector.match?(sibling_node, selector) do
case selector.combinator do
nil -> [sibling_node|acc]
_ ->
{_, _, children_nodes} = sibling_node
traverse(children_nodes, sibling_nodes, selector.combinator.selector, acc)
end
else
acc
end
end
defp traverse_general_sibling(_nodes, sibling_nodes, selector, acc) do
sibling_nodes = Enum.drop_while(sibling_nodes, &ignore_node?/1)
Enum.reduce(sibling_nodes, acc, fn(sibling_node, res_acc) ->
if Selector.match?(sibling_node, selector) do
case selector.combinator do
nil -> [sibling_node|res_acc]
_ ->
{_, _, children_nodes} = sibling_node
traverse(children_nodes, sibling_nodes, selector.combinator.selector, res_acc)
end
else
res_acc
end
end)
end
defp ignore_node?({:comment, _}), do: true
defp ignore_node?({:pi, _, _}), do: true
defp ignore_node?(_), do: false
end