mirror of
https://github.com/RGBCube/serenity
synced 2025-07-25 16:37:35 +00:00
LibWeb: Implement attribute selector case identifier
This commit is contained in:
parent
049d847230
commit
91e1383b85
4 changed files with 82 additions and 12 deletions
|
@ -336,6 +336,7 @@ Result<Selector::SimpleSelector, Parser::ParsingResult> Parser::parse_attribute_
|
|||
// correct with XML later, we'll need to keep the original case and then do
|
||||
// a case-insensitive compare later.
|
||||
.name = attribute_part.token().ident().to_lowercase_string(),
|
||||
.case_type = Selector::SimpleSelector::Attribute::CaseType::DefaultMatch,
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -397,8 +398,30 @@ Result<Selector::SimpleSelector, Parser::ParsingResult> Parser::parse_attribute_
|
|||
simple_selector.attribute().value = value_part.token().is(Token::Type::Ident) ? value_part.token().ident() : value_part.token().string();
|
||||
|
||||
attribute_tokens.skip_whitespace();
|
||||
// Handle case-sensitivity suffixes. https://www.w3.org/TR/selectors-4/#attribute-case
|
||||
if (attribute_tokens.has_next_token()) {
|
||||
auto const& case_sensitivity_part = attribute_tokens.next_token();
|
||||
if (case_sensitivity_part.is(Token::Type::Ident)) {
|
||||
auto case_sensitivity = case_sensitivity_part.token().ident();
|
||||
if (case_sensitivity.equals_ignoring_case("i")) {
|
||||
simple_selector.attribute().case_type = Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch;
|
||||
} else if (case_sensitivity.equals_ignoring_case("s")) {
|
||||
simple_selector.attribute().case_type = Selector::SimpleSelector::Attribute::CaseType::CaseSensitiveMatch;
|
||||
} else {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "Expected a \"i\" or \"s\" attribute selector case sensitivity identifier, got: '{}'", case_sensitivity_part.to_debug_string());
|
||||
return ParsingResult::SyntaxError;
|
||||
}
|
||||
} else {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "Expected an attribute selector case sensitivity identifier, got: '{}'", case_sensitivity_part.to_debug_string());
|
||||
return ParsingResult::SyntaxError;
|
||||
}
|
||||
}
|
||||
|
||||
if (attribute_tokens.has_next_token()) {
|
||||
dbgln_if(CSS_PARSER_DEBUG, "Was not expecting anything else inside attribute selector.");
|
||||
return ParsingResult::SyntaxError;
|
||||
}
|
||||
|
||||
// FIXME: Handle case-sensitivity suffixes. https://www.w3.org/TR/selectors-4/#attribute-case
|
||||
return simple_selector;
|
||||
}
|
||||
|
||||
|
|
|
@ -174,7 +174,20 @@ String Selector::SimpleSelector::serialize() const
|
|||
|
||||
serialize_a_string(s, attribute.value);
|
||||
}
|
||||
// FIXME: 5. If the attribute selector has the case-sensitivity flag present, append " i" (U+0020 U+0069) to s.
|
||||
|
||||
// 5. If the attribute selector has the case-insensitivity flag present, append " i" (U+0020 U+0069) to s.
|
||||
// If the attribute selector has the case-insensitivity flag present, append " s" (U+0020 U+0073) to s.
|
||||
// (the line just above is an addition to CSS OM to match Selectors Level 4 last draft)
|
||||
switch (attribute.case_type) {
|
||||
case Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch:
|
||||
s.append(" i");
|
||||
break;
|
||||
case Selector::SimpleSelector::Attribute::CaseType::CaseSensitiveMatch:
|
||||
s.append(" s");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// 6. Append "]" (U+005D) to s.
|
||||
s.append(']');
|
||||
|
|
|
@ -100,9 +100,15 @@ public:
|
|||
StartsWithString, // [att^=val]
|
||||
EndsWithString, // [att$=val]
|
||||
};
|
||||
enum class CaseType {
|
||||
DefaultMatch,
|
||||
CaseSensitiveMatch,
|
||||
CaseInsensitiveMatch,
|
||||
};
|
||||
MatchType match_type;
|
||||
FlyString name {};
|
||||
String value {};
|
||||
CaseType case_type;
|
||||
};
|
||||
|
||||
Type type;
|
||||
|
|
|
@ -78,17 +78,41 @@ static inline bool matches_checked_pseudo_class(DOM::Element const& element)
|
|||
|
||||
static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute const& attribute, DOM::Element const& element)
|
||||
{
|
||||
switch (attribute.match_type) {
|
||||
case CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute:
|
||||
if (attribute.match_type == CSS::Selector::SimpleSelector::Attribute::MatchType::HasAttribute) {
|
||||
// Early way out in case of an attribute existence selector.
|
||||
return element.has_attribute(attribute.name);
|
||||
}
|
||||
|
||||
auto const case_insensitive_match = (attribute.case_type == CSS::Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch);
|
||||
auto const case_sensitivity = case_insensitive_match
|
||||
? CaseSensitivity::CaseInsensitive
|
||||
: CaseSensitivity::CaseSensitive;
|
||||
|
||||
switch (attribute.match_type) {
|
||||
case CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch:
|
||||
return element.attribute(attribute.name) == attribute.value;
|
||||
case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord:
|
||||
return !attribute.value.is_empty()
|
||||
&& element.attribute(attribute.name).split_view(' ').contains_slow(attribute.value);
|
||||
return case_insensitive_match
|
||||
? element.attribute(attribute.name).equals_ignoring_case(attribute.value)
|
||||
: element.attribute(attribute.name) == attribute.value;
|
||||
case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsWord: {
|
||||
if (attribute.value.is_empty()) {
|
||||
// This selector is always false is match value is empty.
|
||||
return false;
|
||||
}
|
||||
auto const view = element.attribute(attribute.name).split_view(' ');
|
||||
auto const size = view.size();
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
auto const value = view.at(i);
|
||||
if (case_insensitive_match
|
||||
? value.equals_ignoring_case(attribute.value)
|
||||
: value == attribute.value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsString:
|
||||
return !attribute.value.is_empty()
|
||||
&& element.attribute(attribute.name).contains(attribute.value);
|
||||
&& element.attribute(attribute.name).contains(attribute.value, case_sensitivity);
|
||||
case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment: {
|
||||
const auto element_attr_value = element.attribute(attribute.name);
|
||||
if (element_attr_value.is_empty()) {
|
||||
|
@ -100,14 +124,18 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co
|
|||
return false;
|
||||
}
|
||||
auto segments = element_attr_value.split_view('-');
|
||||
return segments.first() == attribute.value;
|
||||
return case_insensitive_match
|
||||
? segments.first().equals_ignoring_case(attribute.value)
|
||||
: segments.first() == attribute.value;
|
||||
}
|
||||
case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithString:
|
||||
return !attribute.value.is_empty()
|
||||
&& element.attribute(attribute.name).starts_with(attribute.value);
|
||||
&& element.attribute(attribute.name).starts_with(attribute.value, case_sensitivity);
|
||||
case CSS::Selector::SimpleSelector::Attribute::MatchType::EndsWithString:
|
||||
return !attribute.value.is_empty()
|
||||
&& element.attribute(attribute.name).ends_with(attribute.value);
|
||||
&& element.attribute(attribute.name).ends_with(attribute.value, case_sensitivity);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue