Current section

86 Versions

Jump to

Compare versions

7 files changed
+38 additions
-16 deletions
  @@ -127,6 +127,7 @@ and a more accurate parser. However `html5ever` is being under active developmen
127 127
128 128 Since it's written in Rust, we need to install Rust and compile the project. Luckily we have have the
129 129 [html5ever Elixir NIF](https://github.com/hansihe/html5ever_elixir) that makes the integration very easy.
130 + For more info, check the article [Rustler - Safe Erlang and Elixir NIFs in Rust](http://hansihe.com/2017/02/05/rustler-safe-erlang-elixir-nifs-in-rust.html).
130 131
131 132 You still need to install Rust in your system. To do that, please
132 133 [follow the instruction](https://www.rust-lang.org/en-US/install.html) presented in the official page.
  @@ -139,7 +140,7 @@ After setup Rust, you need to add `html5ever` NIF to your dependency list:
139 140 defp deps do
140 141 [
141 142 {:floki, "~> 0.14.0"},
142 - {:html5ever, "~> 0.2.0"}
143 + {:html5ever, "~> 0.3.0"}
143 144 ]
144 145 end
145 146 ```
  @@ -156,9 +157,6 @@ config :floki, :html_parser, Floki.HTMLParser.Html5ever
156 157
157 158 After that you are able to use `html5ever` as your HTML parser with Floki.
158 159
159 - To know more about NIFs, check the [Erlang docs](http://erlang.org/doc/man/erl_nif.html).
160 - And to know why Rust NIFs can be better, check the [Rustler](https://github.com/hansihe/rustler) project.
161 -
162 160 ## More about Floki API
163 161
164 162 To parse a HTML document, try:
  @@ -27,4 +27,4 @@
27 27 {<<"name">>,<<"mochiweb">>},
28 28 {<<"optional">>,false},
29 29 {<<"requirement">>,<<"~> 2.15">>}]]}.
30 - {<<"version">>,<<"0.14.0">>}.
30 + {<<"version">>,<<"0.15.0">>}.
  @@ -101,6 +101,8 @@ defmodule Floki.Finder do
101 101 PseudoClass.match_nth_child?(tree, html_node, pseudo_class)
102 102 "first-child" ->
103 103 PseudoClass.match_nth_child?(tree, html_node, %PseudoClass{name: "nth-child", value: 1})
104 + "not" ->
105 + !Selector.match?(html_node, pseudo_class.value)
104 106 unknown_pseudo_class ->
105 107 Logger.warn("Pseudo-class #{inspect unknown_pseudo_class} is not implemented. Ignoring.")
106 108 false
  @@ -110,7 +112,7 @@ defmodule Floki.Finder do
110 112 # The stack serves as accumulator when there is another combinator to traverse.
111 113 # So the scope of one combinator is the stack (or acc) or the parent one.
112 114 defp traverse_with(_, _, []), do: []
113 - defp traverse_with(nil, _, result), do: result
115 + defp traverse_with(nil, _, results), do: results
114 116 defp traverse_with(%Combinator{match_type: :child, selector: s}, tree, stack) do
115 117 results =
116 118 Enum.flat_map(stack, fn(html_node) ->
  @@ -118,7 +120,7 @@ defmodule Floki.Finder do
118 120 |> Enum.reverse
119 121 |> get_nodes(tree)
120 122
121 - Enum.filter(nodes, fn(html_node) -> Selector.match?(html_node, s) end)
123 + Enum.filter(nodes, fn(html_node) -> selector_match?(tree, html_node, s) end)
122 124 end)
123 125
124 126 traverse_with(s.combinator, tree, results)
  @@ -134,7 +136,7 @@ defmodule Floki.Finder do
134 136 nodes = get_nodes(sibling_id, tree)
135 137
136 138 # Finally, try to match those siblings with the selector
137 - Enum.filter(nodes, fn(html_node) -> Selector.match?(html_node, s) end)
139 + Enum.filter(nodes, fn(html_node) -> selector_match?(tree, html_node, s) end)
138 140 end)
139 141
140 142 traverse_with(s.combinator, tree, results)
  @@ -147,7 +149,7 @@ defmodule Floki.Finder do
147 149 nodes = get_nodes(sibling_ids, tree)
148 150
149 151 # Finally, try to match those siblings with the selector
150 - Enum.filter(nodes, fn(html_node) -> Selector.match?(html_node, s) end)
152 + Enum.filter(nodes, fn(html_node) -> selector_match?(tree, html_node, s) end)
151 153 end)
152 154
153 155 traverse_with(s.combinator, tree, results)
  @@ -158,7 +160,7 @@ defmodule Floki.Finder do
158 160 ids_to_match = get_descendant_ids(html_node.node_id, tree)
159 161 nodes = get_nodes(ids_to_match, tree)
160 162
161 - Enum.filter(nodes, fn(html_node) -> Selector.match?(html_node, s) end)
163 + Enum.filter(nodes, fn(html_node) -> selector_match?(tree, html_node, s) end)
162 164 end)
163 165
164 166 traverse_with(s.combinator, tree, results)
  @@ -196,11 +198,12 @@ defmodule Floki.Finder do
196 198 end)
197 199 end
198 200
199 - # finds all descendant node ids recursively through the tree
201 + # finds all descendant node ids recursively through the tree preserving the order
200 202 defp get_descendant_ids(node_id, tree) do
201 203 case get_node(node_id, tree) do
202 204 %{children_nodes_ids: node_ids} ->
203 - Enum.reverse(node_ids) ++ Enum.flat_map(node_ids, &(get_descendant_ids(&1, tree)))
205 + reversed_ids = Enum.reverse(node_ids)
206 + reversed_ids ++ Enum.flat_map(reversed_ids, &(get_descendant_ids(&1, tree)))
204 207 _ ->
205 208 []
206 209 end
  @@ -4,7 +4,7 @@ defmodule Floki.SelectorParser do
4 4 @moduledoc false
5 5 # Parses a list of tokens returned from `SelectorTokenizer` and transfor into a `Selector`.
6 6
7 - alias Floki.{Selector, AttributeSelector, Combinator}
7 + alias Floki.{Selector, SelectorTokenizer, AttributeSelector, Combinator}
8 8 alias Floki.Selector.PseudoClass
9 9
10 10 @attr_match_types [:equal, :dash_match, :includes, :prefix_match, :sufix_match, :substring_match]
  @@ -12,6 +12,10 @@ defmodule Floki.SelectorParser do
12 12 # Returns a `Selector` struct with the parsed selector.
13 13 # Note that this parser does not deal with groups of selectors.
14 14
15 + def parse(selector) when is_binary(selector) do
16 + token_list = SelectorTokenizer.tokenize(selector)
17 + parse(token_list)
18 + end
15 19 def parse(tokens) do
16 20 do_parse(tokens, %Selector{})
17 21 end
  @@ -56,6 +60,23 @@ defmodule Floki.SelectorParser do
56 60 pseudo_class = selector.pseudo_class
57 61 do_parse(t, %{selector | pseudo_class: %{pseudo_class | value: to_string(pattern)}})
58 62 end
63 + defp do_parse([{:pseudo_class_generic_value, _, value} | t], selector) do
64 + s = case selector.pseudo_class do
65 + %PseudoClass{name: "not"} ->
66 + not_selector = parse(to_string(value))
67 +
68 + if not_selector.combinator do
69 + Logger.warn("Only simple selectors are allowed in :not() pseudo-class. Ignoring.")
70 + %{selector | pseudo_class: nil}
71 + else
72 + %{selector | pseudo_class: %{selector.pseudo_class | value: not_selector}}
73 + end
74 + _ ->
75 + selector
76 + end
77 +
78 + do_parse(t, s)
79 + end
59 80 defp do_parse([{:space, _} | t], selector) do
60 81 {t, combinator} = consume_combinator(t, :descendant)
  @@ -3,8 +3,6 @@ defmodule Floki.SelectorTokenizer do
3 3
4 4 # It decodes a given selector and returns the tokens that represents it.
5 5 # Check the rules in "src/floki_selector_lexer.xrl"
6 -
7 - def tokenize(""), do: []
8 6 def tokenize(selector) do
9 7 char_list = selector |> String.strip |> String.to_char_list
Loading more files…