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