Current section

86 Versions

Jump to

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…