Packages
floki
0.33.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
12
files changed
+133
additions
-94
deletions
| @@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. | |
| 7 7 | |
| 8 8 | ## [Unreleased][unreleased] |
| 9 9 | |
| 10 | + ## [0.33.0] - 2022-06-28 |
| 11 | + |
| 12 | + ### Added |
| 13 | + |
| 14 | + - Add support for searching elements that contains text in a case-insensitive manner with |
| 15 | + `fl-icontains` - thanks [@nuno84](https://github.com/nuno84) |
| 16 | + |
| 17 | + ### Changed |
| 18 | + |
| 19 | + - Drop support for Elixir 1.8 and 1.9. |
| 20 | + - Fix and improve internal things - thanks [@derek-zhou](https://github.com/derek-zhou) and [@hissssst](https://github.com/hissssst) |
| 21 | + |
| 10 22 | ## [0.32.1] - 2022-03-24 |
| 11 23 | |
| 12 24 | ### Fixed |
| @@ -596,7 +608,8 @@ of the parent element inside HTML. | |
| 596 608 | |
| 597 609 | - Elixir version requirement from "~> 1.0.0" to ">= 1.0.0". |
| 598 610 | |
| 599 | - [unreleased]: https://github.com/philss/floki/compare/v0.32.1...HEAD |
| 611 | + [unreleased]: https://github.com/philss/floki/compare/v0.33.0...HEAD |
| 612 | + [0.33.0]: https://github.com/philss/floki/compare/v0.32.1...v0.33.0 |
| 600 613 | [0.32.1]: https://github.com/philss/floki/compare/v0.32.0...v0.32.1 |
| 601 614 | [0.32.0]: https://github.com/philss/floki/compare/v0.31.0...v0.32.0 |
| 602 615 | [0.31.0]: https://github.com/philss/floki/compare/v0.30.1...v0.31.0 |
| @@ -61,7 +61,7 @@ Add Floki to your `mix.exs`: | |
| 61 61 | ```elixir |
| 62 62 | defp deps do |
| 63 63 | [ |
| 64 | - {:floki, "~> 0.32.0"} |
| 64 | + {:floki, "~> 0.33.0"} |
| 65 65 | ] |
| 66 66 | end |
| 67 67 | ``` |
| @@ -120,8 +120,8 @@ After Rust is set up, you need to add `html5ever` NIF to your dependency list: | |
| 120 120 | ```elixir |
| 121 121 | defp deps do |
| 122 122 | [ |
| 123 | - {:floki, "~> 0.32.0"}, |
| 124 | - {:html5ever, "~> 0.9.0"} |
| 123 | + {:floki, "~> 0.33.0"}, |
| 124 | + {:html5ever, "~> 0.13.0"} |
| 125 125 | ] |
| 126 126 | end |
| 127 127 | ``` |
| @@ -136,7 +136,7 @@ Then you need to configure your app to use `html5ever`: | |
| 136 136 | config :floki, :html_parser, Floki.HTMLParser.Html5ever |
| 137 137 | ``` |
| 138 138 | |
| 139 | - 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). |
| 139 | + Notice that you can pass the HTML parser as an option in `parse_document/2` and `parse_fragment/2`. |
| 140 140 | |
| 141 141 | #### Using `fast_html` as the HTML parser |
| 142 142 | |
| @@ -148,7 +148,7 @@ First, add `fast_html` to your dependencies: | |
| 148 148 | ```elixir |
| 149 149 | defp deps do |
| 150 150 | [ |
| 151 | - {:floki, "~> 0.32.0"}, |
| 151 | + {:floki, "~> 0.33.0"}, |
| 152 152 | {:fast_html, "~> 2.0"} |
| 153 153 | ] |
| 154 154 | end |
| @@ -259,9 +259,10 @@ Here you find all the [CSS selectors](https://www.w3.org/TR/selectors/#selectors | |
| 259 259 | |
| 260 260 | There are also some selectors based on non-standard specifications. They are: |
| 261 261 | |
| 262 | - | Pattern | Description | |
| 263 | - |----------------------|-----------------------------------------------------| |
| 264 | - | E:fl-contains('foo') | an E element that contains "foo" inside a text node | |
| 262 | + | Pattern | Description | |
| 263 | + |-----------------------|------------------------------------------------------------------------| |
| 264 | + | E:fl-contains('foo') | an E element that contains "foo" inside a text node | |
| 265 | + | E:fl-icontains('foo') | an E element that contains "foo" inside a text node (case insensitive) | |
| 265 266 | |
| 266 267 | ## Special thanks |
| @@ -2,11 +2,10 @@ | |
| 2 2 | {<<"build_tools">>,[<<"mix">>]}. |
| 3 3 | {<<"description">>, |
| 4 4 | <<"Floki is a simple HTML parser that enables search for nodes using CSS selectors.">>}. |
| 5 | - {<<"elixir">>,<<"~> 1.8">>}. |
| 5 | + {<<"elixir">>,<<"~> 1.10">>}. |
| 6 6 | {<<"files">>, |
| 7 | - [<<"lib/floki">>,<<"lib/floki/deep_text.ex">>,<<"lib/floki/entities.ex">>, |
| 8 | - <<"lib/floki/filter_out.ex">>,<<"lib/floki/finder.ex">>, |
| 9 | - <<"lib/floki/flat_text.ex">>,<<"lib/floki/html">>, |
| 7 | + [<<"lib/floki">>,<<"lib/floki/deep_text.ex">>,<<"lib/floki/filter_out.ex">>, |
| 8 | + <<"lib/floki/finder.ex">>,<<"lib/floki/flat_text.ex">>,<<"lib/floki/html">>, |
| 10 9 | <<"lib/floki/html/numeric_charref.ex">>,<<"lib/floki/html/tokenizer.ex">>, |
| 11 10 | <<"lib/floki/html_parser.ex">>,<<"lib/floki/html_parser">>, |
| 12 11 | <<"lib/floki/html_parser/fast_html.ex">>, |
| @@ -15,17 +14,17 @@ | |
| 15 14 | <<"lib/floki/html_tree">>,<<"lib/floki/html_tree/comment.ex">>, |
| 16 15 | <<"lib/floki/html_tree/html_node.ex">>, |
| 17 16 | <<"lib/floki/html_tree/id_seeder.ex">>,<<"lib/floki/html_tree/text.ex">>, |
| 18 | - <<"lib/floki/parse_error.ex">>,<<"lib/floki/raw_html.ex">>, |
| 19 | - <<"lib/floki/selector.ex">>,<<"lib/floki/selector">>, |
| 20 | - <<"lib/floki/selector/attribute_selector.ex">>, |
| 21 | - <<"lib/floki/selector/combinator.ex">>, |
| 22 | - <<"lib/floki/selector/functional.ex">>,<<"lib/floki/selector/parser.ex">>, |
| 17 | + <<"lib/floki/parse_error.ex">>,<<"lib/floki/selector">>, |
| 18 | + <<"lib/floki/selector/functional.ex">>, |
| 23 19 | <<"lib/floki/selector/tokenizer.ex">>, |
| 20 | + <<"lib/floki/selector/attribute_selector.ex">>, |
| 21 | + <<"lib/floki/selector/combinator.ex">>,<<"lib/floki/selector/parser.ex">>, |
| 24 22 | <<"lib/floki/selector/pseudo_class.ex">>,<<"lib/floki/traversal.ex">>, |
| 25 | - <<"lib/floki.ex">>,<<"src/floki_selector_lexer.xrl">>, |
| 26 | - <<"src/floki_mochi_html.erl">>,<<"src/floki.gleam">>,<<"mix.exs">>, |
| 27 | - <<"README.md">>,<<"LICENSE">>,<<"CODE_OF_CONDUCT.md">>, |
| 28 | - <<"CONTRIBUTING.md">>,<<"CHANGELOG.md">>]}. |
| 23 | + <<"lib/floki/raw_html.ex">>,<<"lib/floki/entities.ex">>, |
| 24 | + <<"lib/floki/selector.ex">>,<<"lib/floki.ex">>, |
| 25 | + <<"src/floki_selector_lexer.xrl">>,<<"src/floki_mochi_html.erl">>, |
| 26 | + <<"src/floki.gleam">>,<<"mix.exs">>,<<"README.md">>,<<"LICENSE">>, |
| 27 | + <<"CODE_OF_CONDUCT.md">>,<<"CONTRIBUTING.md">>,<<"CHANGELOG.md">>]}. |
| 29 28 | {<<"licenses">>,[<<"MIT">>]}. |
| 30 29 | {<<"links">>, |
| 31 30 | [{<<"Changelog">>,<<"https://hexdocs.pm/floki/changelog.html">>}, |
| @@ -39,4 +38,4 @@ | |
| 39 38 | {<<"optional">>,false}, |
| 40 39 | {<<"repository">>,<<"hexpm">>}, |
| 41 40 | {<<"requirement">>,<<"~> 0.5.0">>}]]}. |
| 42 | - {<<"version">>,<<"0.32.1">>}. |
| 41 | + {<<"version">>,<<"0.33.0">>}. |
| @@ -746,20 +746,21 @@ defmodule Floki.HTML.Tokenizer do | |
| 746 746 | |
| 747 747 | # § tokenizer-script-data-escape-start-state: re-entrant |
| 748 748 | |
| 749 | - @spec script_data_escape_start(binary(), State.t()) :: State.t() |
| 750 | - def script_data_escape_start(<<?-, html::binary>>, s) do |
| 751 | - script_data_escape_start_dash( |
| 752 | - html, |
| 753 | - %{ |
| 754 | - s |
| 755 | - | tokens: append_char_token(s, @hyphen_minus) |
| 756 | - } |
| 757 | - ) |
| 758 | - end |
| 749 | + ## Unused |
| 750 | + # @spec script_data_escape_start(binary(), State.t()) :: State.t() |
| 751 | + # def script_data_escape_start(<<?-, html::binary>>, s) do |
| 752 | + # script_data_escape_start_dash( |
| 753 | + # html, |
| 754 | + # %{ |
| 755 | + # s |
| 756 | + # | tokens: append_char_token(s, @hyphen_minus) |
| 757 | + # } |
| 758 | + # ) |
| 759 | + # end |
| 759 760 | |
| 760 | - def script_data_escape_start(html, s) do |
| 761 | - script_data(html, s) |
| 762 | - end |
| 761 | + # def script_data_escape_start(html, s) do |
| 762 | + # script_data(html, s) |
| 763 | + # end |
| 763 764 | |
| 764 765 | # § tokenizer-script-data-escape-start-dash-state |
| @@ -49,6 +49,9 @@ defmodule Floki.Selector do | |
| 49 49 | defp classes(%{classes: classes}), do: ".#{Enum.join(classes, ".")}" |
| 50 50 | end |
| 51 51 | |
| 52 | + @wildcards [nil, "*"] |
| 53 | + defguardp is_wildcard(x) when x in @wildcards |
| 54 | + |
| 52 55 | @doc false |
| 53 56 | |
| 54 57 | # Returns if a given node matches with a given selector. |
| @@ -94,31 +97,29 @@ defmodule Floki.Selector do | |
| 94 97 | end) |
| 95 98 | end |
| 96 99 | |
| 97 | - defp namespace_match?(_node, nil), do: true |
| 98 | - defp namespace_match?(_node, "*"), do: true |
| 100 | + defp namespace_match?(_node, namespace) when is_wildcard(namespace), do: true |
| 99 101 | defp namespace_match?(%HTMLNode{type: :pi}, _type), do: false |
| 100 102 | |
| 101 103 | defp namespace_match?(%HTMLNode{type: type_maybe_with_namespace}, namespace) do |
| 102 104 | case String.split(type_maybe_with_namespace, ":") do |
| 103 | - [ns, _type] -> |
| 104 | - ns == namespace |
| 105 | + [^namespace, _type] -> |
| 106 | + true |
| 105 107 | |
| 106 | - [_type] -> |
| 108 | + _ -> |
| 107 109 | false |
| 108 110 | end |
| 109 111 | end |
| 110 112 | |
| 111 | - defp type_match?(_node, nil), do: true |
| 112 | - defp type_match?(_node, "*"), do: true |
| 113 | + defp type_match?(_node, type) when is_wildcard(type), do: true |
| 113 114 | defp type_match?(%HTMLNode{type: :pi}, _type), do: false |
| 114 115 | |
| 115 116 | defp type_match?(%HTMLNode{type: type_maybe_with_namespace}, type) do |
| 116 117 | case String.split(type_maybe_with_namespace, ":") do |
| 117 | - [_ns, tp] -> |
| 118 | - tp == type |
| 118 | + [_ns, ^type] -> |
| 119 | + true |
| 119 120 | |
| 120 | - [tp] -> |
| 121 | - tp == type |
| 121 | + [^type] -> |
| 122 | + true |
| 122 123 | |
| 123 124 | _ -> |
| 124 125 | false |
| @@ -131,11 +132,10 @@ defmodule Floki.Selector do | |
| 131 132 | defp classes_matches?(%HTMLNode{attributes: []}, _), do: false |
| 132 133 | |
| 133 134 | defp classes_matches?(%HTMLNode{attributes: attributes}, classes) do |
| 134 | - Enum.all?(classes, fn class -> |
| 135 | - selector = %AttributeSelector{match_type: :includes, attribute: "class", value: class} |
| 136 | - |
| 137 | - AttributeSelector.match?(attributes, selector) |
| 138 | - end) |
| 135 | + case :proplists.get_value("class", attributes, nil) do |
| 136 | + nil -> false |
| 137 | + class -> classes -- String.split(class, ~r/\s+/) == [] |
| 138 | + end |
| 139 139 | end |
| 140 140 | |
| 141 141 | defp attributes_matches?(_node, []), do: true |
| @@ -210,6 +210,11 @@ defmodule Floki.Selector do | |
| 210 210 | PseudoClass.match_contains?(tree, html_node, pseudo_class) |
| 211 211 | end |
| 212 212 | |
| 213 | + # Case insensitive contains |
| 214 | + defp pseudo_class_match?(html_node, pseudo_class = %{name: "fl-icontains"}, tree) do |
| 215 | + PseudoClass.match_icontains?(tree, html_node, pseudo_class) |
| 216 | + end |
| 217 | + |
| 213 218 | defp pseudo_class_match?(html_node, %{name: "root"}, tree) do |
| 214 219 | PseudoClass.match_root?(html_node, tree) |
| 215 220 | end |
Loading more files…