diff --git a/Base/home/anon/www/attrselectors.html b/Base/home/anon/www/attrselectors.html
new file mode 100644
index 0000000000..dd36f17093
--- /dev/null
+++ b/Base/home/anon/www/attrselectors.html
@@ -0,0 +1,19 @@
+
+
+ CSS attribute selector test
+
+
+
+ This div has id="foo" and is bloo!
+ This div has a "cool" attribute and a cool green color.
+
+
diff --git a/Base/home/anon/www/welcome.html b/Base/home/anon/www/welcome.html
index 9826b9a6e0..dd95f34c96 100644
--- a/Base/home/anon/www/welcome.html
+++ b/Base/home/anon/www/welcome.html
@@ -25,6 +25,7 @@ h1 {
- small
- css
+ - attribute selectors
- lorem ipsum
- presentational hints
- images
diff --git a/Libraries/LibHTML/CSS/Selector.h b/Libraries/LibHTML/CSS/Selector.h
index 56e96a2569..7e1756a9ba 100644
--- a/Libraries/LibHTML/CSS/Selector.h
+++ b/Libraries/LibHTML/CSS/Selector.h
@@ -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&&);
diff --git a/Libraries/LibHTML/CSS/SelectorEngine.cpp b/Libraries/LibHTML/CSS/SelectorEngine.cpp
index 58ef15bd01..904a188e0a 100644
--- a/Libraries/LibHTML/CSS/SelectorEngine.cpp
+++ b/Libraries/LibHTML/CSS/SelectorEngine.cpp
@@ -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;
diff --git a/Libraries/LibHTML/Dump.cpp b/Libraries/LibHTML/Dump.cpp
index 0327783be5..49341b0774 100644
--- a/Libraries/LibHTML/Dump.cpp
+++ b/Libraries/LibHTML/Dump.cpp
@@ -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());
}
}
diff --git a/Libraries/LibHTML/Parser/CSSParser.cpp b/Libraries/LibHTML/Parser/CSSParser.cpp
index c9ced8308d..b3d49bff91 100644
--- a/Libraries/LibHTML/Parser/CSSParser.cpp
+++ b/Libraries/LibHTML/Parser/CSSParser.cpp
@@ -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() == ':') {