1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-23 12:37:40 +00:00

LibWeb: Add namespaces to Attribute selectors

For now, we parse these, but don't actually consider the namespace when
matching them. `DOM::Element` does not (yet) store attribute namespaces
so we can't check what they are.
This commit is contained in:
Sam Atkins 2023-08-08 16:19:20 +01:00 committed by Sam Atkins
parent 1858f06881
commit debf38ee9d
5 changed files with 51 additions and 36 deletions

View file

@ -370,10 +370,9 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_se
return ParseError::SyntaxError;
}
// FIXME: Handle namespace prefix for attribute name.
auto const& attribute_part = attribute_tokens.next_token();
if (!attribute_part.is(Token::Type::Ident)) {
dbgln_if(CSS_PARSER_DEBUG, "Expected ident for attribute name, got: '{}'", attribute_part.to_debug_string());
auto maybe_qualified_name = parse_selector_qualified_name(attribute_tokens, AllowWildcardName::No);
if (!maybe_qualified_name.has_value()) {
dbgln_if(CSS_PARSER_DEBUG, "Expected qualified-name for attribute name, got: '{}'", attribute_tokens.peek_token().to_debug_string());
return ParseError::SyntaxError;
}
@ -386,7 +385,7 @@ Parser::ParseErrorOr<Selector::SimpleSelector> Parser::parse_attribute_simple_se
// they are converted to lowercase, so we do that here too. If we want to be
// correct with XML later, we'll need to keep the original case and then do
// a case-insensitive compare later.
.name = FlyString::from_deprecated_fly_string(attribute_part.token().ident().to_lowercase_string()).release_value_but_fixme_should_propagate_errors(),
.qualified_name = maybe_qualified_name.release_value(),
.case_type = Selector::SimpleSelector::Attribute::CaseType::DefaultMatch,
}
};

View file

@ -155,10 +155,15 @@ ErrorOr<String> Selector::SimpleSelector::serialize() const
// 1. Append "[" (U+005B) to s.
TRY(s.try_append('['));
// FIXME: 2. If the namespace prefix maps to a namespace that is not the null namespace (not in a namespace) append the serialization of the namespace prefix as an identifier, followed by a "|" (U+007C) to s.
// 2. If the namespace prefix maps to a namespace that is not the null namespace (not in a namespace)
// append the serialization of the namespace prefix as an identifier, followed by a "|" (U+007C) to s.
if (attribute.qualified_name.namespace_type == QualifiedName::NamespaceType::Named) {
TRY(serialize_an_identifier(s, attribute.qualified_name.namespace_));
TRY(s.try_append('|'));
}
// 3. Append the serialization of the attribute name as an identifier to s.
TRY(serialize_an_identifier(s, attribute.name));
TRY(serialize_an_identifier(s, attribute.qualified_name.name.name));
// 4. If there is an attribute value specified, append "=", "~=", "|=", "^=", "$=", or "*=" as appropriate (depending on the type of attribute selector),
// followed by the serialization of the attribute value as a string, to s.

View file

@ -177,7 +177,7 @@ public:
CaseInsensitiveMatch,
};
MatchType match_type;
FlyString name {};
QualifiedName qualified_name;
String value {};
CaseType case_type;
};

View file

@ -120,11 +120,15 @@ static inline bool matches_indeterminate_pseudo_class(DOM::Element const& elemen
return false;
}
static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute const& attribute, DOM::Element const& element)
static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute const& attribute, [[maybe_unused]] Optional<CSS::CSSStyleSheet const&> style_sheet_for_rule, DOM::Element const& element)
{
// FIXME: Check the attribute's namespace, once we support that in DOM::Element!
auto attribute_name = attribute.qualified_name.name.name.to_deprecated_fly_string();
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.to_string().to_deprecated_string());
return element.has_attribute(attribute_name);
}
auto const case_insensitive_match = (attribute.case_type == CSS::Selector::SimpleSelector::Attribute::CaseType::CaseInsensitiveMatch);
@ -135,14 +139,14 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co
switch (attribute.match_type) {
case CSS::Selector::SimpleSelector::Attribute::MatchType::ExactValueMatch:
return case_insensitive_match
? Infra::is_ascii_case_insensitive_match(element.attribute(attribute.name.to_string().to_deprecated_string()), attribute.value)
: element.attribute(attribute.name.to_string().to_deprecated_string()) == attribute.value.to_deprecated_string();
? Infra::is_ascii_case_insensitive_match(element.attribute(attribute_name), attribute.value)
: element.attribute(attribute_name) == attribute.value.to_deprecated_string();
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.to_string().to_deprecated_string()).split_view(' ');
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);
@ -156,9 +160,9 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co
}
case CSS::Selector::SimpleSelector::Attribute::MatchType::ContainsString:
return !attribute.value.is_empty()
&& element.attribute(attribute.name.to_string().to_deprecated_string()).contains(attribute.value, case_sensitivity);
&& element.attribute(attribute_name).contains(attribute.value, case_sensitivity);
case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithSegment: {
auto const element_attr_value = element.attribute(attribute.name.to_string().to_deprecated_string());
auto const element_attr_value = element.attribute(attribute_name);
if (element_attr_value.is_empty()) {
// If the attribute value on element is empty, the selector is true
// if the match value is also empty and false otherwise.
@ -174,10 +178,10 @@ static inline bool matches_attribute(CSS::Selector::SimpleSelector::Attribute co
}
case CSS::Selector::SimpleSelector::Attribute::MatchType::StartsWithString:
return !attribute.value.is_empty()
&& element.attribute(attribute.name.to_string().to_deprecated_string()).starts_with(attribute.value, case_sensitivity);
&& 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.to_string().to_deprecated_string()).ends_with(attribute.value, case_sensitivity);
&& element.attribute(attribute_name).ends_with(attribute.value, case_sensitivity);
default:
break;
}
@ -476,7 +480,7 @@ static inline bool matches(CSS::Selector::SimpleSelector const& component, Optio
case CSS::Selector::SimpleSelector::Type::Class:
return element.has_class(component.name());
case CSS::Selector::SimpleSelector::Type::Attribute:
return matches_attribute(component.attribute(), element);
return matches_attribute(component.attribute(), style_sheet_for_rule, element);
case CSS::Selector::SimpleSelector::Type::PseudoClass:
return matches_pseudo_class(component.pseudo_class(), style_sheet_for_rule, element, scope);
case CSS::Selector::SimpleSelector::Type::PseudoElement:

View file

@ -386,6 +386,26 @@ void dump_selector(CSS::Selector const& selector)
dbgln("{}", builder.string_view());
}
static void dump_qualified_name(StringBuilder& builder, CSS::Selector::SimpleSelector::QualifiedName const& qualified_name)
{
StringView namespace_type;
switch (qualified_name.namespace_type) {
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Default:
namespace_type = "Default"sv;
break;
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::None:
namespace_type = "None"sv;
break;
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Any:
namespace_type = "Any"sv;
break;
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Named:
namespace_type = "Named"sv;
break;
}
builder.appendff("NamespaceType={}, Namespace='{}', Name='{}'", namespace_type, qualified_name.namespace_, qualified_name.name.name);
}
void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
{
builder.append(" CSS::Selector:\n"sv);
@ -446,27 +466,12 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
}
builder.appendff("{}:", type_description);
// FIXME: This is goofy
if (simple_selector.value.has<CSS::Selector::SimpleSelector::Name>()) {
builder.append(simple_selector.name());
} else if (simple_selector.value.has<CSS::Selector::SimpleSelector::QualifiedName>()) {
auto qualified_name = simple_selector.qualified_name();
StringView namespace_type;
switch (qualified_name.namespace_type) {
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Default:
namespace_type = "Default"sv;
break;
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::None:
namespace_type = "None"sv;
break;
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Any:
namespace_type = "Any"sv;
break;
case CSS::Selector::SimpleSelector::QualifiedName::NamespaceType::Named:
namespace_type = "Named"sv;
break;
}
builder.appendff(" [NamespaceType={}, Namespace='{}', Name='{}']", namespace_type, qualified_name.namespace_, qualified_name.name.name);
dump_qualified_name(builder, simple_selector.qualified_name());
}
if (simple_selector.type == CSS::Selector::SimpleSelector::Type::PseudoClass) {
@ -681,7 +686,9 @@ void dump_selector(StringBuilder& builder, CSS::Selector const& selector)
break;
}
builder.appendff(" [{}, name='{}', value='{}']", attribute_match_type_description, attribute.name, attribute.value);
builder.appendff(" [{}, ", attribute_match_type_description);
dump_qualified_name(builder, attribute.qualified_name);
builder.appendff(", value='{}']", attribute.value);
}
if (i != relative_selector.simple_selectors.size() - 1)