Current section

86 Versions

Jump to

Compare versions

6 files changed
+54 additions
-60 deletions
  @@ -89,7 +89,7 @@ Add Floki to your `mix.exs`:
89 89 ```elixir
90 90 defp deps do
91 91 [
92 - {:floki, "~> 0.16.0"}
92 + {:floki, "~> 0.17.0"}
93 93 ]
94 94 end
95 95 ```
  @@ -124,7 +124,7 @@ After setup Rust, you need to add `html5ever` NIF to your dependency list:
124 124 ```elixir
125 125 defp deps do
126 126 [
127 - {:floki, "~> 0.16.0"},
127 + {:floki, "~> 0.17.0"},
128 128 {:html5ever, "~> 0.3.0"}
129 129 ]
130 130 end
  @@ -27,4 +27,4 @@
27 27 {<<"name">>,<<"mochiweb">>},
28 28 {<<"optional">>,false},
29 29 {<<"requirement">>,<<"~> 2.15">>}]]}.
30 - {<<"version">>,<<"0.16.0">>}.
30 + {<<"version">>,<<"0.17.0">>}.
  @@ -58,7 +58,7 @@ defmodule Floki.Finder do
58 58
59 59 defp get_selectors(selector_as_string) do
60 60 selector_as_string
61 - |> String.split(",")
61 + |> String.split(~r/,(?![^(]*\))/i)
62 62 |> Enum.map(fn(s) ->
63 63 tokens = SelectorTokenizer.tokenize(s)
  @@ -12,7 +12,7 @@ defmodule Floki.Selector do
12 12 classes: [],
13 13 attributes: [],
14 14 namespace: nil,
15 - pseudo_class: nil,
15 + pseudo_classes: [],
16 16 combinator: nil
17 17
18 18 @doc false
  @@ -23,7 +23,7 @@ defmodule Floki.Selector do
23 23 classes: [],
24 24 attributes: [],
25 25 namespace: nil,
26 - pseudo_class: nil,
26 + pseudo_classes: [],
27 27 combinator: nil}, _tree) do
28 28 false
29 29 end
  @@ -38,7 +38,7 @@ defmodule Floki.Selector do
38 38 && type_match?(html_node, selector.type)
39 39 && classes_matches?(html_node, selector.classes)
40 40 && attributes_matches?(html_node, selector.attributes)
41 - && pseudo_class_match?(html_node, selector.pseudo_class, tree)
41 + && pseudo_classes_match?(html_node, selector.pseudo_classes, tree)
42 42 end
43 43
44 44 defp id_match?(_node, nil), do: true
  @@ -96,7 +96,11 @@ defmodule Floki.Selector do
96 96 end
97 97 end
98 98
99 - defp pseudo_class_match?(_html_node, nil, _tree), do: true
99 + defp pseudo_classes_match?(_html_node, [], _tree), do: true
100 + defp pseudo_classes_match?(html_node, pseudo_classes, tree) do
101 + Enum.all?(pseudo_classes, &pseudo_class_match?(html_node, &1, tree))
102 + end
103 +
100 104 defp pseudo_class_match?(html_node, pseudo_class, tree) do
101 105 case pseudo_class.name do
102 106 "nth-child" ->
  @@ -104,7 +108,7 @@ defmodule Floki.Selector do
104 108 "first-child" ->
105 109 PseudoClass.match_nth_child?(tree, html_node, %PseudoClass{name: "nth-child", value: 1})
106 110 "not" ->
107 - !Selector.match?(html_node, pseudo_class.value, tree)
111 + Enum.all?(pseudo_class.value, &(!Selector.match?(html_node, &1, tree)))
108 112 "fl-contains" ->
109 113 PseudoClass.match_contains?(tree, html_node, pseudo_class)
110 114 unknown_pseudo_class ->
  @@ -42,49 +42,33 @@ defmodule Floki.SelectorParser do
42 42 do_parse(t, %{selector | attributes: [result | selector.attributes]})
43 43 end
44 44 defp do_parse([{:pseudo_not, _} | t], selector) do
45 - {t, pseudo_class} = do_parse_pseudo_not(t, %PseudoClass{name: "not"})
46 -
47 - do_parse(t, %{selector | pseudo_class: pseudo_class})
45 + {t, pseudo_not_class} = do_parse_pseudo_not(t, %PseudoClass{name: "not", value: []})
46 + pseudo_classes = Enum.reject([pseudo_not_class | selector.pseudo_classes], &is_nil(&1))
47 + do_parse(t, %{selector | pseudo_classes: pseudo_classes})
48 48 end
49 49 defp do_parse([{:pseudo, _, pseudo_class} | t], selector) do
50 - do_parse(t, %{selector | pseudo_class: %PseudoClass{name: to_string(pseudo_class)}})
50 + pseudo_classes = [%PseudoClass{name: to_string(pseudo_class)} | selector.pseudo_classes]
51 + do_parse(t, %{selector | pseudo_classes: pseudo_classes})
51 52 end
52 53 defp do_parse([{:pseudo_class_int, _, pseudo_class_int} | t], selector) do
53 - pseudo_class = selector.pseudo_class
54 - do_parse(t, %{selector | pseudo_class: %{pseudo_class | value: pseudo_class_int}})
54 + [pseudo_class | pseudo_classes] = selector.pseudo_classes
55 + do_parse(t, %{selector | pseudo_classes: [%{pseudo_class | value: pseudo_class_int} | pseudo_classes]})
55 56 end
56 57 defp do_parse([{:pseudo_class_even, _} | t], selector) do
57 - pseudo_class = selector.pseudo_class
58 - do_parse(t, %{selector | pseudo_class: %{pseudo_class | value: "even"}})
58 + [pseudo_class | pseudo_classes] = selector.pseudo_classes
59 + do_parse(t, %{selector | pseudo_classes: [%{pseudo_class | value: "even"} | pseudo_classes]})
59 60 end
60 61 defp do_parse([{:pseudo_class_odd, _} | t], selector) do
61 - pseudo_class = selector.pseudo_class
62 - do_parse(t, %{selector | pseudo_class: %{pseudo_class | value: "odd"}})
62 + [pseudo_class | pseudo_classes] = selector.pseudo_classes
63 + do_parse(t, %{selector | pseudo_classes: [%{pseudo_class | value: "odd"} | pseudo_classes]})
63 64 end
64 65 defp do_parse([{:pseudo_class_pattern, _, pattern} | t], selector) do
65 - pseudo_class = selector.pseudo_class
66 - do_parse(t, %{selector | pseudo_class: %{pseudo_class | value: to_string(pattern)}})
66 + [pseudo_class | pseudo_classes] = selector.pseudo_classes
67 + do_parse(t, %{selector | pseudo_classes: [%{pseudo_class | value: to_string(pattern)} | pseudo_classes]})
67 68 end
68 69 defp do_parse([{:pseudo_class_quoted, _, pattern} | t], selector) do
69 - pseudo_class = selector.pseudo_class
70 - do_parse(t, %{selector | pseudo_class: %{pseudo_class | value: to_string(pattern)}})
71 - end
72 - defp do_parse([{:pseudo_class_generic_value, _, value} | t], selector) do
73 - s = case selector.pseudo_class do
74 - %PseudoClass{name: "not"} ->
75 - not_selector = parse(to_string(value))
76 -
77 - if not_selector.combinator do
78 - Logger.warn("Only simple selectors are allowed in :not() pseudo-class. Ignoring.")
79 - %{selector | pseudo_class: nil}
80 - else
81 - %{selector | pseudo_class: %{selector.pseudo_class | value: not_selector}}
82 - end
83 - _ ->
84 - selector
85 - end
86 -
87 - do_parse(t, s)
70 + [pseudo_class | pseudo_classes] = selector.pseudo_classes
71 + do_parse(t, %{selector | pseudo_classes: [%{pseudo_class | value: to_string(pattern)} | pseudo_classes]})
88 72 end
89 73 defp do_parse([{:space, _} | t], selector) do
90 74 {t, combinator} = consume_combinator(t, :descendant)
  @@ -157,28 +141,34 @@ defmodule Floki.SelectorParser do
157 141 defp do_parse_pseudo_not([{:close_parentesis, _} | t], pseudo_class) do
158 142 {t, pseudo_class}
159 143 end
144 + defp do_parse_pseudo_not([{:space, _} | t], pseudo_class) do
145 + do_parse_pseudo_not(t, pseudo_class)
146 + end
160 147 defp do_parse_pseudo_not(tokens, pseudo_class) do
161 - {after_parentesis, before_parentesis} = split_list_for_not_tokens(tokens, [])
162 -
163 - selector = before_parentesis
164 - |> Enum.reverse
165 - |> parse
166 -
167 - if selector.combinator do
168 - Logger.warn("Only simple selectors are allowed in :not() pseudo-class. Ignoring.")
169 - {after_parentesis, nil}
170 - else
171 - {after_parentesis, %{pseudo_class | value: selector}}
172 - end
148 + do_parse_pseudo_not(tokens, %Selector{}, pseudo_class)
149 + end
150 + defp do_parse_pseudo_not([{:close_parentesis, _} | t], pseudo_not_selector, pseudo_class) do
151 + pseudo_class = update_pseudo_not_value(pseudo_class, pseudo_not_selector)
152 + {t, pseudo_class}
153 + end
154 + defp do_parse_pseudo_not([{:comma, _} | t], pseudo_not_selector, pseudo_class) do
155 + pseudo_class = update_pseudo_not_value(pseudo_class, pseudo_not_selector)
156 + do_parse_pseudo_not(t, pseudo_class)
157 + end
158 + defp do_parse_pseudo_not([{:space, _} | t], pseudo_not_selector, pseudo_class) do
159 + do_parse_pseudo_not(t, pseudo_not_selector, pseudo_class)
160 + end
161 + defp do_parse_pseudo_not([next_token | t], pseudo_not_selector, pseudo_class) do
162 + pseudo_not_selector = do_parse([next_token], pseudo_not_selector)
163 + do_parse_pseudo_not(t, pseudo_not_selector, pseudo_class)
173 164 end
174 165
175 - defp split_list_for_not_tokens([{:close_parentesis, _} | t], tokens_before) do
176 - {t, tokens_before}
166 + defp update_pseudo_not_value(pseudo_class, pseudo_not_selector = %Selector{combinator: nil}) do
167 + pseudo_not_value = [pseudo_not_selector | Map.get(pseudo_class, :value, [])]
168 + %{pseudo_class | value: pseudo_not_value}
177 169 end
178 - defp split_list_for_not_tokens([], tokens_before) do
179 - {[], tokens_before}
180 - end
181 - defp split_list_for_not_tokens([h | t], tokens_before) do
182 - split_list_for_not_tokens(t, [h | tokens_before])
170 + defp update_pseudo_not_value(_pseudo_class, _pseudo_not_selector) do
171 + Logger.warn("Only simple selectors are allowed in :not() pseudo-class. Ignoring.")
172 + nil
183 173 end
184 174 end
Loading more files…