Packages
floki
0.17.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
86 Versions
Jump to
Current section
86 Versions
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…