Current section

86 Versions

Jump to

Compare versions

9 files changed
+144 additions
-35 deletions
  @@ -8,7 +8,7 @@
8 8 <<"lib/floki/combinator.ex">>,<<"lib/floki/deep_text.ex">>,
9 9 <<"lib/floki/filter_out.ex">>,<<"lib/floki/finder.ex">>,
10 10 <<"lib/floki/flat_text.ex">>,<<"lib/floki/html_tree.ex">>,
11 - <<"lib/floki/html_tree/html_node.ex">>,
11 + <<"lib/floki/html_tree/comment.ex">>,<<"lib/floki/html_tree/html_node.ex">>,
12 12 <<"lib/floki/html_tree/id_seeder.ex">>,<<"lib/floki/html_tree/text.ex">>,
13 13 <<"lib/floki/parser.ex">>,<<"lib/floki/selector.ex">>,
14 14 <<"lib/floki/selector/pseudo_class.ex">>,<<"lib/floki/selector_parser.ex">>,
  @@ -25,4 +25,4 @@
25 25 {<<"name">>,<<"mochiweb">>},
26 26 {<<"optional">>,false},
27 27 {<<"requirement">>,<<"~> 2.15">>}]]}.
28 - {<<"version">>,<<"0.12.1">>}.
28 + {<<"version">>,<<"0.13.0">>}.
  @@ -1,5 +1,5 @@
1 1 defmodule Floki do
2 - alias Floki.{Finder, Parser, FilterOut}
2 + alias Floki.{Finder, Parser, FilterOut, HTMLTree}
3 3
4 4 @moduledoc """
5 5 Floki is a simple HTML parser that enables search for nodes using CSS selectors.
  @@ -141,10 +141,16 @@ defmodule Floki do
141 141 @spec find(binary | html_tree, binary) :: html_tree
142 142
143 143 def find(html, selector) when is_binary(html) do
144 - html |> parse |> Finder.find(selector)
144 + html_as_tuple = parse(html)
145 +
146 + {tree, results} = Finder.find(html_as_tuple, selector)
147 +
148 + Enum.map(results, fn(html_node) -> HTMLTree.to_tuple(tree, html_node) end)
145 149 end
146 - def find(html_tree, selector) do
147 - Finder.find(html_tree, selector)
150 + def find(html_tree_as_tuple, selector) do
151 + {tree, results} = Finder.find(html_tree_as_tuple, selector)
152 +
153 + Enum.map(results, fn(html_node) -> HTMLTree.to_tuple(tree, html_node) end)
148 154 end
149 155
150 156 def transform(html_tree_list, transformation) when is_list(html_tree_list) do
  @@ -1,6 +1,8 @@
1 1 defmodule Floki.FilterOut do
2 2 @moduledoc false
3 3
4 + alias Floki.{HTMLTree, Finder}
5 +
4 6 # Helper functions for filtering out a specific element from the tree.
5 7
6 8 @type html_tree :: tuple | list
  @@ -8,8 +10,31 @@ defmodule Floki.FilterOut do
8 10
9 11 @spec filter_out(html_tree, selector) :: tuple | list
10 12
13 + def filter_out(html_tree, :comment) do
14 + mapper(html_tree, :comment)
15 + end
11 16 def filter_out(html_tree, selector) do
12 - mapper(html_tree, selector)
17 + case Finder.find(html_tree, selector) do
18 + {:empty_tree, _} ->
19 + html_tree
20 + {tree, results} ->
21 + new_tree = Enum.reduce(results, tree, fn(html_node, tree) ->
22 + HTMLTree.delete_node(tree, html_node)
23 + end)
24 +
25 + html_as_tuples = Enum.map(new_tree.root_nodes_ids, fn(node_id) ->
26 + root = Map.get(new_tree.nodes, node_id)
27 +
28 + HTMLTree.to_tuple(new_tree, root)
29 + end)
30 +
31 + case html_tree do
32 + tree when is_tuple(tree) ->
33 + hd(html_as_tuples)
34 + trees when is_list(trees) ->
35 + html_as_tuples
36 + end
37 + end
13 38 end
14 39
15 40 defp filter({nodetext, _, _}, selector) when nodetext === selector, do: false
  @@ -8,8 +8,8 @@ defmodule Floki.Finder do
8 8
9 9 alias Floki.{Combinator, Selector, SelectorParser, SelectorTokenizer}
10 10 alias Floki.HTMLTree
11 + alias Floki.HTMLTree.HTMLNode
11 12 alias Floki.Selector.PseudoClass
12 - alias Floki.HTMLTree.Text
13 13
14 14 @type html_tree :: tuple | list
15 15 @type selector :: binary | %Selector{} | [%Selector{}]
  @@ -19,8 +19,8 @@ defmodule Floki.Finder do
19 19
20 20 @spec find(html_tree, selector) :: html_tree
21 21
22 - def find([], _), do: []
23 - def find(html_as_string, _) when is_binary(html_as_string), do: []
22 + def find([], _), do: {:empty_tree, []}
23 + def find(html_as_string, _) when is_binary(html_as_string), do: {:empty_tree, []}
24 24 def find(html_tree, selector_as_string) when is_binary(selector_as_string) do
25 25 selectors = get_selectors(selector_as_string)
26 26 find_selectors(html_tree, selectors)
  @@ -47,11 +47,13 @@ defmodule Floki.Finder do
47 47 defp find_selectors(html_tuple_or_list, selectors) do
48 48 tree = HTMLTree.build(html_tuple_or_list)
49 49
50 - tree.node_ids
51 - |> Enum.reverse
52 - |> get_nodes(tree)
53 - |> Enum.flat_map(fn(html_node) -> get_matches_for_selectors(tree, html_node, selectors) end)
54 - |> Enum.map(fn(html_node) -> as_tuple(tree, html_node) end)
50 + results =
51 + tree.node_ids
52 + |> Enum.reverse
53 + |> get_nodes(tree)
54 + |> Enum.flat_map(fn(html_node) -> get_matches_for_selectors(tree, html_node, selectors) end)
55 +
56 + {tree, results}
55 57 end
56 58
57 59 defp get_selectors(selector_as_string) do
  @@ -171,17 +173,28 @@ defmodule Floki.Finder do
171 173 Map.get(tree.nodes, id)
172 174 end
173 175
176 + defp get_sibling_ids_from([], _html_node), do: []
177 + defp get_sibling_ids_from(ids, html_node) do
178 + ids
179 + |> Enum.reverse
180 + |> Enum.drop_while(fn(id) -> id != html_node.node_id end)
181 + |> tl()
182 + end
174 183 defp get_siblings(html_node, tree) do
175 184 parent = get_node(html_node.parent_node_id, tree)
176 185
177 - if parent do
178 - [_html_node_id | sibling_ids] = parent.children_nodes_ids
179 - |> Enum.reverse
180 - |> Enum.drop_while(fn(id) -> id != html_node.node_id end)
181 - sibling_ids
182 - else
183 - []
184 - end
186 + ids = if parent do
187 + get_sibling_ids_from(parent.children_nodes_ids, html_node)
188 + else
189 + get_sibling_ids_from(tree.root_nodes_ids, html_node)
190 + end
191 +
192 + Enum.filter(ids, fn(id) ->
193 + case get_node(id, tree) do
194 + %HTMLNode{} -> true
195 + _ -> false
196 + end
197 + end)
185 198 end
186 199
187 200 # finds all descendant node ids recursively through the tree
  @@ -193,13 +206,4 @@ defmodule Floki.Finder do
193 206 []
194 207 end
195 208 end
196 -
197 - defp as_tuple(_tree, %Text{content: text}), do: text
198 - defp as_tuple(tree, html_node) do
199 - children = html_node.children_nodes_ids
200 - |> Enum.reverse
201 - |> Enum.map(fn(id) -> as_tuple(tree, get_node(id, tree)) end)
202 -
203 - {html_node.type, html_node.attributes, children}
204 - end
205 209 end
  @@ -9,7 +9,7 @@ defmodule Floki.HTMLTree do
9 9 defstruct nodes: %{}, root_nodes_ids: [], node_ids: []
10 10
11 11 alias Floki.HTMLTree
12 - alias Floki.HTMLTree.{HTMLNode, Text, IDSeeder}
12 + alias Floki.HTMLTree.{HTMLNode, Text, Comment, IDSeeder}
13 13
14 14 def build({tag, attrs, children}) do
15 15 root_id = IDSeeder.seed([])
  @@ -38,6 +38,55 @@ defmodule Floki.HTMLTree do
38 38 Enum.reduce(html_tuples, %HTMLTree{}, reducer)
39 39 end
40 40
41 + def delete_node(tree, html_node) do
42 + do_delete(tree, [html_node], [])
43 + end
44 +
45 + def to_tuple(_tree, %Text{content: text}), do: text
46 + def to_tuple(_tree, %Comment{content: comment}), do: {:comment, comment}
47 + def to_tuple(tree, html_node) do
48 + children = html_node.children_nodes_ids
49 + |> Enum.reverse
50 + |> Enum.map(fn(id) -> to_tuple(tree, Map.get(tree.nodes, id)) end)
51 +
52 + {html_node.type, html_node.attributes, children}
53 + end
54 +
55 + defp do_delete(tree, [], []), do: tree
56 + defp do_delete(tree, [html_node | t], stack_ids) do
57 + new_tree_nodes = delete_node_from_nodes(tree.nodes, html_node)
58 +
59 + ids_for_stack = get_ids_for_delete_stack(html_node)
60 +
61 + do_delete(%{tree | nodes: new_tree_nodes,
62 + node_ids: List.delete(tree.node_ids, html_node.node_id),
63 + root_nodes_ids: List.delete(tree.root_nodes_ids, html_node.node_id)},
64 + t, ids_for_stack ++ stack_ids)
65 + end
66 + defp do_delete(tree, [], stack_ids) do
67 + html_nodes = tree.nodes
68 + |> Map.take(stack_ids)
69 + |> Map.values
70 +
71 + do_delete(tree, html_nodes, [])
72 + end
73 +
74 + defp delete_node_from_nodes(nodes, html_node) do
75 + tree_nodes = Map.delete(nodes, html_node.node_id)
76 + parent_node = Map.get(nodes, html_node.parent_node_id)
77 +
78 + if parent_node do
79 + children_ids = List.delete(parent_node.children_nodes_ids, html_node.node_id)
80 + new_parent = %{parent_node | children_nodes_ids: children_ids}
81 + %{tree_nodes | new_parent.node_id => new_parent}
82 + else
83 + tree_nodes
84 + end
85 + end
86 +
87 + defp get_ids_for_delete_stack(%HTMLNode{children_nodes_ids: ids}), do: ids
88 + defp get_ids_for_delete_stack(_), do: []
89 +
41 90 defp build_tree(tree, [], _, []), do: tree
42 91 defp build_tree(tree, [{tag, attrs, child_children} | children], parent_id, stack) do
43 92 new_id = IDSeeder.seed(tree.node_ids)
  @@ -51,7 +100,15 @@ defmodule Floki.HTMLTree do
51 100 %{tree | nodes: nodes, node_ids: [new_id | tree.node_ids]}
52 101 |> build_tree(child_children, new_id, [{parent_id, children} | stack])
53 102 end
103 + defp build_tree(tree, [{:comment, comment} | children], parent_id, stack) do
104 + new_id = IDSeeder.seed(tree.node_ids)
105 + new_node = %Comment{content: comment, node_id: new_id, parent_node_id: parent_id}
54 106
107 + nodes = put_new_node(tree.nodes, new_node)
108 +
109 + %{tree | nodes: nodes, node_ids: [new_id | tree.node_ids]}
110 + |> build_tree(children, parent_id, stack)
111 + end
55 112 defp build_tree(tree, [text | children], parent_id, stack) when is_binary(text) do
56 113 new_id = IDSeeder.seed(tree.node_ids)
57 114 new_node = %Text{content: text, node_id: new_id, parent_node_id: parent_id}
Loading more files…