From 306b9dfb51b4fbdecad2ae29fb954327d50cf0a5 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sun, 6 Oct 2019 09:28:10 +0200 Subject: [PATCH] LibHTML: Various little improvements to the CSS parser The parser now kinda recognizes immediate child selectors, !important, and various other little things. It's still extremely far from robust and/or correct. :^) --- Libraries/LibHTML/CSS/Selector.h | 9 ++- Libraries/LibHTML/CSS/StyleDeclaration.h | 1 + Libraries/LibHTML/Dump.cpp | 10 ++- Libraries/LibHTML/Parser/CSSParser.cpp | 95 ++++++++++++++++++++---- 4 files changed, 100 insertions(+), 15 deletions(-) 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;