LibHTML: Implement some attribute selector support
This patch adds a[foo] and a[foo=bar] attribute selectors. Note that an attribute selector is an optional part of a selector component, and not a component on its own.
This commit is contained in:
parent
54a6ae9201
commit
8946e50986
Notes:
sideshowbarker
2024-07-19 11:07:23 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/8946e509861
6 changed files with 115 additions and 8 deletions
19
Base/home/anon/www/attrselectors.html
Normal file
19
Base/home/anon/www/attrselectors.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>CSS attribute selector test</title>
|
||||
<style type="text/css">
|
||||
div[id="foo"] {
|
||||
background-color: blue;
|
||||
color: #fff;
|
||||
}
|
||||
div[cool] {
|
||||
background-color: green;
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="foo">This div has id="foo" and is bloo!</div>
|
||||
<div cool="">This div has a "cool" attribute and a cool green color.</div>
|
||||
</body>
|
||||
</html>
|
|
@ -25,6 +25,7 @@ h1 {
|
|||
<ul>
|
||||
<li><a href="small.html">small</a></li>
|
||||
<li><a href="css.html">css</a></li>
|
||||
<li><a href="attrselectors.html">attribute selectors</a></li>
|
||||
<li><a href="lorem.html">lorem ipsum</a></li>
|
||||
<li><a href="phint.html">presentational hints</a></li>
|
||||
<li><a href="images.html">images</a></li>
|
||||
|
|
|
@ -33,6 +33,16 @@ public:
|
|||
Relation relation { Relation::None };
|
||||
|
||||
String value;
|
||||
|
||||
enum class AttributeMatchType {
|
||||
None,
|
||||
HasAttribute,
|
||||
ExactValueMatch,
|
||||
};
|
||||
|
||||
AttributeMatchType attribute_match_type { AttributeMatchType::None };
|
||||
String attribute_name;
|
||||
String attribute_value;
|
||||
};
|
||||
|
||||
explicit Selector(Vector<Component>&&);
|
||||
|
|
|
@ -29,6 +29,19 @@ bool matches(const Selector::Component& component, const Element& element)
|
|||
break;
|
||||
}
|
||||
|
||||
switch (component.attribute_match_type) {
|
||||
case Selector::Component::AttributeMatchType::HasAttribute:
|
||||
if (!element.has_attribute(component.attribute_name))
|
||||
return false;
|
||||
break;
|
||||
case Selector::Component::AttributeMatchType::ExactValueMatch:
|
||||
if (element.attribute(component.attribute_name) != component.attribute_value)
|
||||
return false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
switch (component.type) {
|
||||
case Selector::Component::Type::Universal:
|
||||
return true;
|
||||
|
|
|
@ -180,12 +180,28 @@ void dump_rule(const StyleRule& rule)
|
|||
relation_description = "{GeneralSibling}";
|
||||
break;
|
||||
}
|
||||
dbgprintf(" %s:%s %s\n", type_description, component.value.characters(), relation_description);
|
||||
const char* attribute_match_type_description = "";
|
||||
switch (component.attribute_match_type) {
|
||||
case Selector::Component::AttributeMatchType::None:
|
||||
break;
|
||||
case Selector::Component::AttributeMatchType::HasAttribute:
|
||||
attribute_match_type_description = "HasAttribute";
|
||||
break;
|
||||
case Selector::Component::AttributeMatchType::ExactValueMatch:
|
||||
attribute_match_type_description = "ExactValueMatch";
|
||||
break;
|
||||
}
|
||||
|
||||
dbgprintf(" %s:%s %s", type_description, component.value.characters(), relation_description);
|
||||
if (component.attribute_match_type != Selector::Component::AttributeMatchType::None) {
|
||||
dbgprintf(" [%s, name='%s', value='%s']", attribute_match_type_description, component.attribute_name.characters(), component.attribute_value.characters());
|
||||
}
|
||||
dbgprintf("\n");
|
||||
}
|
||||
}
|
||||
dbgprintf(" Declarations:\n");
|
||||
for (auto& property : rule.declaration().properties()) {
|
||||
dbgprintf(" CSS::PropertyID(%u): '%s'\n", (unsigned)property.property_id, property.value->to_string().characters());
|
||||
dbgprintf(" %s: '%s'\n", CSS::string_from_property_id(property.property_id), property.value->to_string().characters());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -227,7 +227,15 @@ public:
|
|||
if (peek() == '*') {
|
||||
type = Selector::Component::Type::Universal;
|
||||
consume_one();
|
||||
return Selector::Component { type, Selector::Component::PseudoClass::None, relation, String() };
|
||||
return Selector::Component {
|
||||
type,
|
||||
Selector::Component::PseudoClass::None,
|
||||
relation,
|
||||
String(),
|
||||
Selector::Component::AttributeMatchType::None,
|
||||
String(),
|
||||
String()
|
||||
};
|
||||
}
|
||||
|
||||
if (peek() == '.') {
|
||||
|
@ -244,15 +252,55 @@ public:
|
|||
buffer.append(consume_one());
|
||||
|
||||
PARSE_ASSERT(!buffer.is_null());
|
||||
Selector::Component component { type, Selector::Component::PseudoClass::None, relation, String::copy(buffer) };
|
||||
Selector::Component component {
|
||||
type,
|
||||
Selector::Component::PseudoClass::None,
|
||||
relation,
|
||||
String::copy(buffer),
|
||||
Selector::Component::AttributeMatchType::None,
|
||||
String(),
|
||||
String()
|
||||
};
|
||||
buffer.clear();
|
||||
|
||||
if (peek() == '[') {
|
||||
// FIXME: Implement attribute selectors.
|
||||
while (peek() != ']') {
|
||||
consume_one();
|
||||
Selector::Component::AttributeMatchType attribute_match_type = Selector::Component::AttributeMatchType::HasAttribute;
|
||||
String attribute_name;
|
||||
String attribute_value;
|
||||
bool in_value = false;
|
||||
consume_specific('[');
|
||||
char expected_end_of_attribute_selector = ']';
|
||||
while (peek() != expected_end_of_attribute_selector) {
|
||||
char ch = consume_one();
|
||||
if (ch == '=') {
|
||||
attribute_match_type = Selector::Component::AttributeMatchType::ExactValueMatch;
|
||||
attribute_name = String::copy(buffer);
|
||||
buffer.clear();
|
||||
in_value = true;
|
||||
consume_whitespace_or_comments();
|
||||
if (peek() == '\'') {
|
||||
expected_end_of_attribute_selector = '\'';
|
||||
consume_one();
|
||||
} else if (peek() == '"') {
|
||||
expected_end_of_attribute_selector = '"';
|
||||
consume_one();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
buffer.append(ch);
|
||||
}
|
||||
consume_one();
|
||||
if (in_value)
|
||||
attribute_value = String::copy(buffer);
|
||||
else
|
||||
attribute_name = String::copy(buffer);
|
||||
buffer.clear();
|
||||
component.attribute_match_type = attribute_match_type;
|
||||
component.attribute_name = attribute_name;
|
||||
component.attribute_value = attribute_value;
|
||||
if (expected_end_of_attribute_selector != ']')
|
||||
consume_specific(expected_end_of_attribute_selector);
|
||||
consume_whitespace_or_comments();
|
||||
consume_specific(']');
|
||||
}
|
||||
|
||||
if (peek() == ':') {
|
||||
|
|
Loading…
Add table
Reference in a new issue