Packages
floki
0.15.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
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…