ソースを参照

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. :^)
Andreas Kling 5 年 前
コミット
306b9dfb51

+ 8 - 1
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;
     };
 

+ 1 - 0
Libraries/LibHTML/CSS/StyleDeclaration.h

@@ -6,6 +6,7 @@
 struct StyleProperty {
     String name;
     NonnullRefPtr<StyleValue> value;
+    bool important { false };
 };
 
 class StyleDeclaration : public RefCounted<StyleDeclaration> {

+ 9 - 1
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");

+ 80 - 11
Libraries/LibHTML/Parser/CSSParser.cpp

@@ -3,6 +3,13 @@
 #include <ctype.h>
 #include <stdio.h>
 
+#define PARSE_ASSERT(x) \
+    if (!(x)) { \
+        dbg() << "CSS PARSER ASSERTION FAILED: " << #x; \
+        dbg() << "At character# " << index << " in CSS: _" << css << "_"; \
+        ASSERT_NOT_REACHED(); \
+    }
+
 static Optional<Color> 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<Selector::Component> 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());
+        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();
+        }
 
-        auto component_string = String::copy(buffer);
+        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<Selector::Component> components;
-        components.append({ type, component_string });
-        buffer.clear();
+
+        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<StyleProperty> 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;