Current section

86 Versions

Jump to

Compare versions

6 files changed
+92 additions
-91 deletions
  @@ -60,7 +60,7 @@ Add Floki to your `mix.exs`:
60 60 ```elixir
61 61 defp deps do
62 62 [
63 - {:floki, "~> 0.25.0"}
63 + {:floki, "~> 0.26.0"}
64 64 ]
65 65 end
66 66 ```
  @@ -79,7 +79,7 @@ The packages names may be different depending on your OS.
79 79 ### Alternative HTML parsers
80 80
81 81 By default Floki uses a patched version of `mochiweb_html` for parsing fragments
82 - due to it's ease of installation (it's written in Erlang and has no outside dependencies).
82 + due to its ease of installation (it's written in Erlang and has no outside dependencies).
83 83
84 84 However one might want to use an alternative parser due to the following
85 85 concerns:
  @@ -100,7 +100,7 @@ Floki supports the following alternative parsers:
100 100
101 101 `fast_html` is generally faster, according to the
102 102 [benchmarks](https://hexdocs.pm/fast_html/readme.html#benchmarks) conducted by
103 - it's developers. Though `html5ever` does have an advantage on really small
103 + its developers. Though `html5ever` does have an advantage on really small
104 104 (~4kb) fragments due to it being implemented as a NIF.
105 105
106 106 #### Using `html5ever` as the HTML parser
  @@ -113,7 +113,7 @@ After Rust is set up, you need to add `html5ever` NIF to your dependency list:
113 113 ```elixir
114 114 defp deps do
115 115 [
116 - {:floki, "~> 0.25.0"},
116 + {:floki, "~> 0.26.0"},
117 117 {:html5ever, "~> 0.7.0"}
118 118 ]
119 119 end
  @@ -145,7 +145,7 @@ First, add `fast_html` to your dependencies:
145 145 ```elixir
146 146 defp deps do
147 147 [
148 - {:floki, "~> 0.25.0"},
148 + {:floki, "~> 0.26.0"},
149 149 {:fast_html, "~> 1.0"}
150 150 ]
151 151 end
  @@ -236,9 +236,11 @@ Here you find all the [CSS selectors](https://www.w3.org/TR/selectors/#selectors
236 236 | E[foo*="bar"] | an E element whose "foo" attribute value contains the substring "bar" |
237 237 | E[foo\|="en"] | an E element whose "foo" attribute has a hyphen-separated list of values beginning (from the left) with "en" |
238 238 | E:nth-child(n) | an E element, the n-th child of its parent |
239 + | E:nth-last-child(n) | an E element, the n-th child of its parent, counting from bottom to up |
239 240 | E:first-child | an E element, first child of its parent |
240 241 | E:last-child | an E element, last child of its parent |
241 242 | E:nth-of-type(n) | an E element, the n-th child of its type among its siblings |
243 + | E:nth-last-of-type(n) | an E element, the n-th child of its type among its siblings, counting from bottom to up |
242 244 | E:first-of-type | an E element, first child of its type among its siblings |
243 245 | E:last-of-type | an E element, last child of its type among its siblings |
244 246 | E.warning | an E element whose class is "warning" |
  @@ -32,4 +32,4 @@
32 32 {<<"optional">>,false},
33 33 {<<"repository">>,<<"hexpm">>},
34 34 {<<"requirement">>,<<"~> 0.5.0">>}]]}.
35 - {<<"version">>,<<"0.25.0">>}.
35 + {<<"version">>,<<"0.26.0">>}.
  @@ -388,7 +388,7 @@ defmodule Floki do
388 388 html_tree(),
389 389 traverse_acc(),
390 390 (html_tag(), traverse_acc() -> {html_tag() | nil, traverse_acc()})
391 - ) :: html_tree()
391 + ) :: {html_tree(), traverse_acc()}
392 392
393 393 defdelegate traverse_and_update(html_tree, acc, fun), to: Floki.Traversal
  @@ -151,7 +151,14 @@ defmodule Floki.Selector do
151 151 end
152 152
153 153 defp pseudo_class_match?(html_node, %{name: "last-child"}, tree) do
154 - PseudoClass.match_nth_child?(tree, html_node, %PseudoClass{name: "nth-child", value: -1})
154 + PseudoClass.match_nth_last_child?(tree, html_node, %PseudoClass{
155 + name: "nth-last-child",
156 + value: 1
157 + })
158 + end
159 +
160 + defp pseudo_class_match?(html_node, pseudo_class = %{name: "nth-last-child"}, tree) do
161 + PseudoClass.match_nth_last_child?(tree, html_node, pseudo_class)
155 162 end
156 163
157 164 defp pseudo_class_match?(html_node, pseudo_class = %{name: "nth-of-type"}, tree) do
  @@ -166,12 +173,16 @@ defmodule Floki.Selector do
166 173 end
167 174
168 175 defp pseudo_class_match?(html_node, %{name: "last-of-type"}, tree) do
169 - PseudoClass.match_nth_of_type?(tree, html_node, %PseudoClass{
170 - name: "nth-of-type",
171 - value: -1
176 + PseudoClass.match_nth_last_of_type?(tree, html_node, %PseudoClass{
177 + name: "nth-last-of-type",
178 + value: 1
172 179 })
173 180 end
174 181
182 + defp pseudo_class_match?(html_node, pseudo_class = %{name: "nth-last-of-type"}, tree) do
183 + PseudoClass.match_nth_last_of_type?(tree, html_node, pseudo_class)
184 + end
185 +
175 186 defp pseudo_class_match?(html_node, pseudo_class = %{name: "not"}, tree) do
176 187 Enum.all?(pseudo_class.value, &(!Selector.match?(html_node, &1, tree)))
177 188 end
  @@ -23,75 +23,54 @@ defmodule Floki.Selector.PseudoClass do
23 23 end
24 24 end
25 25
26 - def match_nth_child?(_, %HTMLNode{parent_node_id: nil}, _), do: false
26 + def match_nth_child?(_tree, %HTMLNode{parent_node_id: nil}, _pseudo_class), do: false
27 27
28 - def match_nth_child?(tree, html_node, %__MODULE__{value: -1}) do
29 - children_nodes_ids = get_children_nodes_indexed(tree, html_node.parent_node_id)
30 - {last_child_id, _} = Enum.max_by(children_nodes_ids, fn {_, pos} -> pos end)
31 - last_child_id == html_node.node_id
32 - end
28 + def match_nth_child?(tree, html_node, %__MODULE__{value: value}) do
29 + relative_position =
30 + tree
31 + |> children_nodes(html_node.parent_node_id)
32 + |> Enum.with_index(1)
33 + |> node_position(html_node)
33 34
34 - def match_nth_child?(tree, html_node, %__MODULE__{value: position}) when is_integer(position) do
35 - node_position(tree, html_node) == position
36 - end
37 -
38 - def match_nth_child?(tree, html_node, %__MODULE__{value: "even"}) do
39 - position = node_position(tree, html_node)
40 - rem(position, 2) == 0
41 - end
42 -
43 - def match_nth_child?(tree, html_node, %__MODULE__{value: "odd"}) do
44 - position = node_position(tree, html_node)
45 - rem(position, 2) == 1
46 - end
47 -
48 - def match_nth_child?(tree, html_node, %__MODULE__{value: %Functional{stream: s}}) do
49 - position = node_position(tree, html_node)
50 - position in s
51 - end
52 -
53 - def match_nth_child?(_tree, _html_node, %__MODULE__{value: expression}) do
54 - Logger.info(fn ->
55 - "Pseudo-class nth-child with expressions like #{inspect(expression)} are not supported yet. Ignoring."
56 - end)
57 -
58 - false
35 + match_position?(value, relative_position, "nth-child")
59 36 end
60 37
61 38 def match_nth_of_type?(_, %HTMLNode{parent_node_id: nil}, _), do: false
62 39
63 - def match_nth_of_type?(tree, html_node, %__MODULE__{value: -1}) do
64 - children_nodes_ids = get_children_nodes_indexed_by_type(tree, html_node)
65 - {last_child_id, _} = Enum.max_by(children_nodes_ids, fn {_, pos} -> pos end)
66 - last_child_id == html_node.node_id
40 + def match_nth_of_type?(tree, html_node, %__MODULE__{value: value}) do
41 + relative_position =
42 + tree
43 + |> children_nodes(html_node.parent_node_id)
44 + |> filter_nodes_by_type(tree.nodes, html_node.type)
45 + |> Enum.with_index(1)
46 + |> node_position(html_node)
47 +
48 + match_position?(value, relative_position, "nth-of-type")
67 49 end
68 50
69 - def match_nth_of_type?(tree, html_node, %__MODULE__{value: position})
70 - when is_integer(position) do
71 - node_type_position(tree, html_node) == position
51 + def match_nth_last_child?(_, %HTMLNode{parent_node_id: nil}, _), do: false
52 +
53 + def match_nth_last_child?(tree, html_node, %__MODULE__{value: value}) do
54 + relative_position =
55 + tree
56 + |> reverse_children_nodes(html_node.parent_node_id)
57 + |> Enum.with_index(1)
58 + |> node_position(html_node)
59 +
60 + match_position?(value, relative_position, "nth-last-child")
72 61 end
73 62
74 - def match_nth_of_type?(tree, html_node, %__MODULE__{value: "even"}) do
75 - position = node_type_position(tree, html_node)
76 - rem(position, 2) == 0
77 - end
63 + def match_nth_last_of_type?(_, %HTMLNode{parent_node_id: nil}, _), do: false
78 64
79 - def match_nth_of_type?(tree, html_node, %__MODULE__{value: "odd"}) do
80 - position = node_type_position(tree, html_node)
81 - rem(position, 2) == 1
82 - end
65 + def match_nth_last_of_type?(tree, html_node, %__MODULE__{value: value}) do
66 + relative_position =
67 + tree
68 + |> reverse_children_nodes(html_node.parent_node_id)
69 + |> filter_nodes_by_type(tree.nodes, html_node.type)
70 + |> Enum.with_index(1)
71 + |> node_position(html_node)
83 72
84 - def match_nth_of_type?(tree, html_node, %__MODULE__{value: %Functional{stream: s}}) do
85 - position = node_type_position(tree, html_node)
86 - position in s
87 - end
88 -
89 - def match_nth_of_type?(_, _, %__MODULE__{value: expression}) do
90 - Logger.info(fn ->
91 - "Pseudo-class nth-of-type with expressions like #{inspect(expression)} are not supported yet. Ignoring."
92 - end)
93 -
94 - false
73 + match_position?(value, relative_position, "nth-last-of-type")
95 74 end
96 75
97 76 def match_contains?(tree, html_node, %__MODULE__{value: value}) do
  @@ -106,34 +85,36 @@ defmodule Floki.Selector.PseudoClass do
106 85 res != nil
107 86 end
108 87
109 - defp node_position(tree, html_node) do
110 - children_nodes_ids = get_children_nodes_indexed(tree, html_node.parent_node_id)
111 - find_node_position(children_nodes_ids, html_node.node_id)
88 + defp match_position?(value, relative_position, name) do
89 + case value do
90 + position when is_integer(position) ->
91 + relative_position == position
92 +
93 + "even" ->
94 + rem(relative_position, 2) == 0
95 +
96 + "odd" ->
97 + rem(relative_position, 2) == 1
98 +
99 + %Functional{stream: s} ->
100 + relative_position in s
101 +
102 + expression ->
103 + Logger.info(fn ->
104 + "Pseudo-class #{name} with expressions like #{inspect(expression)} are not supported yet. Ignoring."
105 + end)
106 +
107 + false
108 + end
112 109 end
113 110
114 - defp node_type_position(tree, html_node) do
115 - children_nodes_ids = get_children_nodes_indexed_by_type(tree, html_node)
116 - find_node_position(children_nodes_ids, html_node.node_id)
117 - end
118 -
119 - defp find_node_position(ids, node_id) do
111 + defp node_position(ids, %HTMLNode{node_id: node_id}) do
120 112 {_node_id, position} = Enum.find(ids, fn {id, _} -> id == node_id end)
121 113
122 114 position
123 115 end
124 116
125 - defp get_children_nodes_indexed(tree, parent_node_id) do
126 - nodes = get_children_nodes(tree, parent_node_id)
127 - Enum.with_index(nodes, 1)
128 - end
129 -
130 - defp get_children_nodes_indexed_by_type(tree, html_node) do
131 - get_children_nodes(tree, html_node.parent_node_id)
132 - |> filter_nodes_by_type(tree.nodes, html_node.type)
133 - |> Enum.with_index(1)
134 - end
135 -
136 - defp get_children_nodes(tree, parent_node_id) do
117 + defp children_nodes(tree, parent_node_id) do
137 118 parent_node = Map.get(tree.nodes, parent_node_id)
138 119
139 120 parent_node.children_nodes_ids
  @@ -141,6 +122,13 @@ defmodule Floki.Selector.PseudoClass do
141 122 |> filter_only_html_nodes(tree.nodes)
142 123 end
143 124
125 + defp reverse_children_nodes(tree, parent_node_id) do
126 + parent_node = Map.get(tree.nodes, parent_node_id)
127 +
128 + parent_node.children_nodes_ids
129 + |> filter_only_html_nodes(tree.nodes)
130 + end
131 +
144 132 defp filter_only_html_nodes(ids, nodes) do
145 133 Enum.filter(ids, fn id ->
146 134 case Map.get(nodes, id) do
Loading more files…