Packages
floki
0.35.4
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/attribute_selector.ex
defmodule Floki.Selector.AttributeSelector do
@moduledoc false
# It is very similar to the `Selector` module, but is specialized in attributes
# and attribute selectors.
alias Floki.Selector.AttributeSelector
defstruct match_type: nil, attribute: nil, value: nil, flag: nil
@type match_type ::
nil
| :equal
| :includes
| :dash_match
| :prefix_match
| :suffix_match
| :substring_match
@type t :: %__MODULE__{
match_type: match_type(),
attribute: String.t(),
value: String.t() | nil,
flag: String.t() | nil
}
defimpl String.Chars do
def to_string(selector) do
"[#{selector.attribute}#{type(selector.match_type)}'#{selector.value}'#{flag(selector.flag)}]"
end
defp type(match_type) do
case match_type do
:equal -> "="
:includes -> "~="
:dash_match -> "|="
:prefix_match -> "^="
:suffix_match -> "$="
:substring_match -> "*="
_ -> ""
end
end
defp flag(nil), do: ""
defp flag(flag), do: " " <> flag
end
# Returns if attributes of a node matches with a given attribute selector.
def match?(attributes, s = %AttributeSelector{match_type: nil, value: nil})
when is_list(attributes) or is_map(attributes) do
attribute_present?(s.attribute, attributes)
end
# Case-insensitive matches
def match?(attributes, s = %AttributeSelector{match_type: :equal, flag: "i"}) do
String.downcase(get_value(s.attribute, attributes)) == String.downcase(s.value)
end
def match?(attributes, s = %AttributeSelector{match_type: :includes, flag: "i"}) do
selector_value = String.downcase(s.value)
s.attribute
|> get_value(attributes)
# Splits by whitespaces ("a b c" -> ["a", "b", "c"])
|> String.split([" ", "\t", "\n"], trim: true)
|> Enum.any?(fn v -> String.downcase(v) == selector_value end)
end
def match?(attributes, s = %AttributeSelector{match_type: :dash_match, flag: "i"}) do
selector_value = String.downcase(s.value)
value = String.downcase(get_value(s.attribute, attributes))
value == selector_value || String.starts_with?(value, "#{selector_value}-")
end
def match?(attributes, s = %AttributeSelector{match_type: :prefix_match, flag: "i"}) do
s.attribute
|> get_value(attributes)
|> String.downcase()
|> String.starts_with?(String.downcase(s.value))
end
def match?(attributes, s = %AttributeSelector{match_type: :suffix_match, flag: "i"}) do
s.attribute
|> get_value(attributes)
|> String.downcase()
|> String.ends_with?(String.downcase(s.value))
end
def match?(attributes, s = %AttributeSelector{match_type: :substring_match, flag: "i"}) do
s.attribute
|> get_value(attributes)
|> String.downcase()
|> String.contains?(String.downcase(s.value))
end
# Case-sensitive matches
def match?(attributes, s = %AttributeSelector{match_type: :equal}) do
get_value(s.attribute, attributes) == s.value
end
def match?(attributes, s = %AttributeSelector{match_type: :includes, value: value}) do
get_value(s.attribute, attributes)
|> String.split([" ", "\t", "\n"], trim: true)
|> Enum.member?(value)
end
def match?(attributes, s = %AttributeSelector{match_type: :dash_match}) do
value = get_value(s.attribute, attributes)
value == s.value || String.starts_with?(value, "#{s.value}-")
end
def match?(attributes, s = %AttributeSelector{match_type: :prefix_match}) do
s.attribute |> get_value(attributes) |> String.starts_with?(s.value)
end
def match?(attributes, s = %AttributeSelector{match_type: :suffix_match}) do
s.attribute |> get_value(attributes) |> String.ends_with?(s.value)
end
def match?(attributes, s = %AttributeSelector{match_type: :substring_match}) do
s.attribute |> get_value(attributes) |> String.contains?(s.value)
end
defp get_value(attr_name, attributes) do
Enum.find_value(attributes, "", fn
{^attr_name, value} -> value
_ -> false
end)
end
defp attribute_present?(name, attributes) do
Enum.any?(attributes, fn
{^name, _v} -> true
_ -> false
end)
end
end