Packages
floki
0.17.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
Files
Jump to
Current section
Files
lib/floki/selector.ex
defmodule Floki.Selector do
require Logger
@moduledoc false
# Represents a CSS selector. It also have functions to match nodes with a given selector.
alias Floki.{Selector, AttributeSelector}
alias Floki.Selector.PseudoClass
alias Floki.HTMLTree.{HTMLNode, Text, Comment}
defstruct id: nil,
type: nil,
classes: [],
attributes: [],
namespace: nil,
pseudo_classes: [],
combinator: nil
@doc false
# Returns if a given node matches with a given selector.
def match?(_node, %Selector{id: nil,
type: nil,
classes: [],
attributes: [],
namespace: nil,
pseudo_classes: [],
combinator: nil}, _tree) do
false
end
def match?(nil, _selector, _tree), do: false
def match?({:comment, _comment}, _selector, _tree), do: false
def match?({:pi, _xml, _xml_attrs}, _selector, _tree), do: false
def match?(%Text{}, _selector, _tree), do: false
def match?(%Comment{}, _selector, _tree), do: false
def match?(html_node, selector, tree) do
id_match?(html_node, selector.id)
&& namespace_match?(html_node, selector.namespace)
&& type_match?(html_node, selector.type)
&& classes_matches?(html_node, selector.classes)
&& attributes_matches?(html_node, selector.attributes)
&& pseudo_classes_match?(html_node, selector.pseudo_classes, tree)
end
defp id_match?(_node, nil), do: true
defp id_match?(%HTMLNode{attributes: []}, _), do: false
defp id_match?(%HTMLNode{attributes: attributes}, id) do
Enum.any? attributes, fn(attribute) ->
case attribute do
{"id", ^id} -> true
_ -> false
end
end
end
defp namespace_match?(_node, nil), do: true
defp namespace_match?(_node, "*"), do: true
defp namespace_match?(%HTMLNode{type: :pi}, _type), do: false
defp namespace_match?(%HTMLNode{type: type_maybe_with_namespace}, namespace) do
case String.split(type_maybe_with_namespace, ":") do
[ns, _type] ->
ns == namespace
[_type] -> false
end
end
defp type_match?(_node, nil), do: true
defp type_match?(_node, "*"), do: true
defp type_match?(%HTMLNode{type: :pi}, _type), do: false
defp type_match?(%HTMLNode{type: type_maybe_with_namespace}, type) do
case String.split(type_maybe_with_namespace, ":") do
[_ns, tp] ->
tp == type
[tp] ->
tp == type
end
end
defp type_match?(_, _), do: false
defp classes_matches?(_node, []), do: true
defp classes_matches?(%HTMLNode{attributes: []}, _), do: false
defp classes_matches?(%HTMLNode{attributes: attributes}, classes) do
Enum.all? classes, fn(class) ->
selector = %AttributeSelector{match_type: :includes,
attribute: "class",
value: class}
AttributeSelector.match?(attributes, selector)
end
end
defp attributes_matches?(_node, []), do: true
defp attributes_matches?(%HTMLNode{attributes: []}, _), do: false
defp attributes_matches?(%HTMLNode{attributes: attributes}, attributes_selectors) do
Enum.all? attributes_selectors, fn(attribute_selector) ->
AttributeSelector.match?(attributes, attribute_selector)
end
end
defp pseudo_classes_match?(_html_node, [], _tree), do: true
defp pseudo_classes_match?(html_node, pseudo_classes, tree) do
Enum.all?(pseudo_classes, &pseudo_class_match?(html_node, &1, tree))
end
defp pseudo_class_match?(html_node, pseudo_class, tree) do
case pseudo_class.name do
"nth-child" ->
PseudoClass.match_nth_child?(tree, html_node, pseudo_class)
"first-child" ->
PseudoClass.match_nth_child?(tree, html_node, %PseudoClass{name: "nth-child", value: 1})
"not" ->
Enum.all?(pseudo_class.value, &(!Selector.match?(html_node, &1, tree)))
"fl-contains" ->
PseudoClass.match_contains?(tree, html_node, pseudo_class)
unknown_pseudo_class ->
Logger.warn("Pseudo-class #{inspect unknown_pseudo_class} is not implemented. Ignoring.")
false
end
end
end