diff options
author | Daniel Glazman <daniel@glazman.org> | 2022-03-29 18:01:36 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2022-03-29 18:53:20 +0200 |
commit | 91e1383b857c4a8bc55ec4e9c931eea217810cbe (patch) | |
tree | cd68dc976880d43db5a3b782fd5354b7cd512ee1 | |
parent | 049d847230e034a485fa8f2e99c593ef5b4078bd (diff) | |
download | serenity-91e1383b857c4a8bc55ec4e9c931eea217810cbe.zip |
LibWeb: Implement attribute selector case identifier
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 25 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Selector.cpp | 15 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Selector.h | 6 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp | 48 |
4 files changed, 82 insertions, 12 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index fa52c3ad0b..1ead40652e 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -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; } diff --git a/Userland/Libraries/LibWeb/CSS/Selector.cpp b/Userland/Libraries/LibWeb/CSS/Selector.cpp index 98115e71c4..832267748c 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.cpp +++ b/Userland/Libraries/LibWeb/CSS/Selector.cpp @@ -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(']'); diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h index 14f65fd95e..1fc242e2da 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.h +++ b/Userland/Libraries/LibWeb/CSS/Selector.h @@ -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; diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp index 0801e0432f..4bfc3562d3 100644 --- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp @@ -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; |