Skip to content

Commit

Permalink
Find without html tree for some pseudo classes (#568)
Browse files Browse the repository at this point in the history
This commit adds support for traversal without building HTMLTree for the pseudo-classes
that don't require tree information - :checked, :disabled, and :not not using any of the
 other pseudo-classes.
  • Loading branch information
ypconstante authored May 21, 2024
1 parent c3417a5 commit 90eb5da
Show file tree
Hide file tree
Showing 2 changed files with 28 additions and 6 deletions.
22 changes: 16 additions & 6 deletions lib/floki/finder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule Floki.Finder do

alias Floki.{HTMLTree, Selector}
alias HTMLTree.HTMLNode
alias Selector.PseudoClass
import Floki, only: [is_html_node: 1]

# Find elements inside a HTML tree.
Expand Down Expand Up @@ -56,13 +57,13 @@ defmodule Floki.Finder do
# - single selector
# - single child or adjacent sibling combinator, and as the last combinator
# - no pseudo classes
defp traverse_html_tuples?([selector]), do: traverse_html_tuples?(selector)
defp traverse_html_tuples?(selectors) when is_list(selectors), do: false
defp traverse_html_tuples?(%Selector{pseudo_classes: [_ | _]}), do: false
defp traverse_html_tuples?(%Selector{combinator: nil}), do: true
defp traverse_html_tuples?([%Selector{} = selector]), do: traverse_html_tuples?(selector)

defp traverse_html_tuples?(%Selector{combinator: combinator}),
do: traverse_html_tuples?(combinator)
defp traverse_html_tuples?(%Selector{combinator: nil, pseudo_classes: pseudo_classes}),
do: traverse_html_tuples?(pseudo_classes)

defp traverse_html_tuples?(%Selector{combinator: combinator, pseudo_classes: pseudo_classes}),
do: traverse_html_tuples?(pseudo_classes) and traverse_html_tuples?(combinator)

defp traverse_html_tuples?(%Selector.Combinator{match_type: match_type, selector: selector})
when match_type in [:descendant, :general_sibling],
Expand All @@ -75,6 +76,15 @@ defmodule Floki.Finder do
when match_type in [:child, :adjacent_sibling],
do: traverse_html_tuples?(selector)

defp traverse_html_tuples?([%PseudoClass{name: name} | rest])
when name in ["checked", "disabled"],
do: traverse_html_tuples?(rest)

defp traverse_html_tuples?([%PseudoClass{name: "not", value: value} | rest]) do
Enum.all?(value, &traverse_html_tuples?(&1)) and traverse_html_tuples?(rest)
end

defp traverse_html_tuples?([]), do: true
defp traverse_html_tuples?(_), do: false

# The stack serves as accumulator when there is another combinator to traverse.
Expand Down
12 changes: 12 additions & 0 deletions lib/floki/selector/pseudo_class.ex
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,14 @@ defmodule Floki.Selector.PseudoClass do
attribute_is_present?(html_node.attributes, "selected")
end

def match_checked?({"input", attributes, _children}) do
attribute_is_present?(attributes, "checked")
end

def match_checked?({"option", attributes, _children}) do
attribute_is_present?(attributes, "selected")
end

def match_checked?(_), do: false

defp attribute_is_present?(attributes, attribute_name) when is_list(attributes) do
Expand All @@ -134,6 +142,10 @@ defmodule Floki.Selector.PseudoClass do
attribute_is_present?(html_node.attributes, "disabled")
end

def match_disabled?({type, attributes, _children}) when type in @disableable_html_nodes do
attribute_is_present?(attributes, "disabled")
end

def match_disabled?(_html_node), do: false

def match_root?(html_node, tree) do
Expand Down

0 comments on commit 90eb5da

Please sign in to comment.