diff --git a/Libraries/LibHTML/CSS/Selector.h b/Libraries/LibHTML/CSS/Selector.h
index 9199a3dc00..1648a911f4 100644
--- a/Libraries/LibHTML/CSS/Selector.h
+++ b/Libraries/LibHTML/CSS/Selector.h
@@ -11,9 +11,16 @@ public:
Invalid,
TagName,
Id,
- Class
+ Class,
};
Type type { Type::Invalid };
+
+ enum class Relation {
+ None,
+ ImmediateChild,
+ };
+ Relation relation { Relation::None };
+
String value;
};
diff --git a/Libraries/LibHTML/CSS/StyleDeclaration.h b/Libraries/LibHTML/CSS/StyleDeclaration.h
index b5ec084c86..9dd6c70509 100644
--- a/Libraries/LibHTML/CSS/StyleDeclaration.h
+++ b/Libraries/LibHTML/CSS/StyleDeclaration.h
@@ -6,6 +6,7 @@
struct StyleProperty {
String name;
NonnullRefPtr value;
+ bool important { false };
};
class StyleDeclaration : public RefCounted {
diff --git a/Libraries/LibHTML/Dump.cpp b/Libraries/LibHTML/Dump.cpp
index 8cb17f3572..0252c061b7 100644
--- a/Libraries/LibHTML/Dump.cpp
+++ b/Libraries/LibHTML/Dump.cpp
@@ -148,7 +148,15 @@ void dump_rule(const StyleRule& rule)
type_description = "TagName";
break;
}
- dbgprintf(" %s:%s\n", type_description, component.value.characters());
+ const char* relation_description = "";
+ switch (component.relation) {
+ case Selector::Component::Relation::None:
+ break;
+ case Selector::Component::Relation::ImmediateChild:
+ relation_description = "{ImmediateChild}";
+ break;
+ }
+ dbgprintf(" %s:%s %s\n", type_description, component.value.characters(), relation_description);
}
}
dbgprintf(" Declarations:\n");
diff --git a/Libraries/LibHTML/Parser/CSSParser.cpp b/Libraries/LibHTML/Parser/CSSParser.cpp
index 5914ec2c36..19ed848277 100644
--- a/Libraries/LibHTML/Parser/CSSParser.cpp
+++ b/Libraries/LibHTML/Parser/CSSParser.cpp
@@ -3,6 +3,13 @@
#include
#include
+#define PARSE_ASSERT(x) \
+ if (!(x)) { \
+ dbg() << "CSS PARSER ASSERTION FAILED: " << #x; \
+ dbg() << "At character# " << index << " in CSS: _" << css << "_"; \
+ ASSERT_NOT_REACHED(); \
+ }
+
static Optional parse_css_color(const StringView& view)
{
auto color = Color::from_string(view);
@@ -53,7 +60,7 @@ public:
char consume_specific(char ch)
{
- ASSERT(peek() == ch);
+ PARSE_ASSERT(peek() == ch);
++index;
return ch;
}
@@ -71,13 +78,23 @@ public:
bool is_valid_selector_char(char ch) const
{
- return isalnum(ch) || ch == '-' || ch == '_';
+ return isalnum(ch) || ch == '-' || ch == '_' || ch == '(' || ch == ')' || ch == '@';
}
- void parse_selector()
+ Optional parse_selector_component()
{
consume_whitespace();
Selector::Component::Type type;
+ Selector::Component::Relation relation = Selector::Component::Relation::None;
+
+ if (peek() == '{')
+ return {};
+
+ if (peek() == '>') {
+ relation = Selector::Component::Relation::ImmediateChild;
+ consume_one();
+ consume_whitespace();
+ }
if (peek() == '.') {
type = Selector::Component::Type::Class;
@@ -92,13 +109,43 @@ public:
while (is_valid_selector_char(peek()))
buffer.append(consume_one());
- ASSERT(!buffer.is_null());
-
- auto component_string = String::copy(buffer);
-
- Vector components;
- components.append({ type, component_string });
+ PARSE_ASSERT(!buffer.is_null());
+ Selector::Component component { type, relation, String::copy(buffer) };
buffer.clear();
+
+ if (peek() == '[') {
+ // FIXME: Implement attribute selectors.
+ while (peek() != ']') {
+ consume_one();
+ }
+ consume_one();
+ }
+
+ if (peek() == ':') {
+ // FIXME: Implement pseudo stuff.
+ consume_one();
+ if (peek() == ':')
+ consume_one();
+ while (is_valid_selector_char(peek()))
+ consume_one();
+ }
+
+ return component;
+ }
+
+ void parse_selector()
+ {
+ Vector components;
+
+ for (;;) {
+ auto component = parse_selector_component();
+ if (component.has_value())
+ components.append(component.value());
+ consume_whitespace();
+ if (peek() == ',' || peek() == '{')
+ break;
+ }
+
current_rule.selectors.append(Selector(move(components)));
};
@@ -123,12 +170,16 @@ public:
bool is_valid_property_value_char(char ch) const
{
- return !isspace(ch) && ch != ';';
+ return ch != '!' && ch != ';';
}
- void parse_property()
+ Optional parse_property()
{
consume_whitespace();
+ if (peek() == ';') {
+ consume_one();
+ return {};
+ }
buffer.clear();
while (is_valid_property_name_char(peek()))
buffer.append(consume_one());
@@ -141,14 +192,32 @@ public:
buffer.append(consume_one());
auto property_value = String::copy(buffer);
buffer.clear();
+ consume_whitespace();
+ bool is_important = false;
+ if (peek() == '!') {
+ consume_specific('!');
+ consume_specific('i');
+ consume_specific('m');
+ consume_specific('p');
+ consume_specific('o');
+ consume_specific('r');
+ consume_specific('t');
+ consume_specific('a');
+ consume_specific('n');
+ consume_specific('t');
+ consume_whitespace();
+ is_important = true;
+ }
consume_specific(';');
- current_rule.properties.append({ property_name, parse_css_value(property_value) });
+ return StyleProperty { property_name, parse_css_value(property_value), is_important };
}
void parse_declaration()
{
for (;;) {
- parse_property();
+ auto property = parse_property();
+ if (property.has_value())
+ current_rule.properties.append(property.value());
consume_whitespace();
if (peek() == '}')
break;