浏览代码

LibWeb: Add selector support to the new CSSParser

This is stolen from the old parser, but it seems to parse fine :^)
stelar7 4 年之前
父节点
当前提交
55446172cb

+ 224 - 1
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -13,6 +13,7 @@
 #include <LibWeb/CSS/Parser/StyleComponentValueRule.h>
 #include <LibWeb/CSS/Parser/StyleComponentValueRule.h>
 #include <LibWeb/CSS/Parser/StyleFunctionRule.h>
 #include <LibWeb/CSS/Parser/StyleFunctionRule.h>
 #include <LibWeb/CSS/Selector.h>
 #include <LibWeb/CSS/Selector.h>
+#include <LibWeb/Dump.h>
 
 
 #define CSS_PARSER_TRACE 1
 #define CSS_PARSER_TRACE 1
 
 
@@ -76,6 +77,8 @@ Vector<QualifiedStyleRule> Parser::parse_as_stylesheet()
         dbgln("");
         dbgln("");
 
 
         auto selectors = parse_selectors(rule.m_prelude);
         auto selectors = parse_selectors(rule.m_prelude);
+        CSS::Selector selector = Selector(move(selectors));
+        dump_selector(selector);
     }
     }
 
 
     return rules;
     return rules;
@@ -83,8 +86,223 @@ Vector<QualifiedStyleRule> Parser::parse_as_stylesheet()
 
 
 Vector<CSS::Selector::ComplexSelector> Parser::parse_selectors(Vector<String> parts)
 Vector<CSS::Selector::ComplexSelector> Parser::parse_selectors(Vector<String> parts)
 {
 {
-    (void)parts;
+    // TODO:
+    // This is a mess because the prelude is parsed as a string.
+    // It should really be parsed as its class, but the cpp gods have forsaken me
+    // and i cant make it work due to cyclic includes.
+
     Vector<CSS::Selector::ComplexSelector> selectors;
     Vector<CSS::Selector::ComplexSelector> selectors;
+
+    size_t index = 0;
+    auto parse_simple_selector = [&]() -> Optional<CSS::Selector::SimpleSelector> {
+        if (index >= parts.size()) {
+            return {};
+        }
+
+        auto currentToken = parts.at(index);
+        CSS::Selector::SimpleSelector::Type type;
+        if (currentToken == "*") {
+            type = CSS::Selector::SimpleSelector::Type::Universal;
+            index++;
+            return CSS::Selector::SimpleSelector {
+                type,
+                CSS::Selector::SimpleSelector::PseudoClass::None,
+                CSS::Selector::SimpleSelector::PseudoElement::None,
+                String(),
+                CSS::Selector::SimpleSelector::AttributeMatchType::None,
+                String(),
+                String()
+            };
+        }
+
+        if (currentToken == ".") {
+            type = CSS::Selector::SimpleSelector::Type::Class;
+        } else if (currentToken == "#") {
+            type = CSS::Selector::SimpleSelector::Type::Id;
+        } else if (currentToken == "*") {
+            type = CSS::Selector::SimpleSelector::Type::Universal;
+        } else {
+            type = CSS::Selector::SimpleSelector::Type::TagName;
+        }
+
+        index++;
+        auto value = currentToken;
+
+        if (type == CSS::Selector::SimpleSelector::Type::TagName) {
+            value = value.to_lowercase();
+        }
+
+        CSS::Selector::SimpleSelector simple_selector {
+            type,
+            CSS::Selector::SimpleSelector::PseudoClass::None,
+            CSS::Selector::SimpleSelector::PseudoElement::None,
+            value,
+            CSS::Selector::SimpleSelector::AttributeMatchType::None,
+            String(),
+            String()
+        };
+
+        if (index >= parts.size()) {
+            return simple_selector;
+        }
+
+        currentToken = parts.at(index);
+        if (currentToken.starts_with('[')) {
+            auto adjusted = currentToken.substring(1, currentToken.length() - 2);
+
+            // TODO: split on String :^)
+            Vector<String> attribute_parts = adjusted.split(',');
+
+            simple_selector.attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::HasAttribute;
+            simple_selector.attribute_name = attribute_parts.first();
+
+            size_t attribute_index = 1;
+            if (attribute_index >= attribute_parts.size()) {
+                return simple_selector;
+            }
+
+            if (attribute_parts.at(attribute_index) == " =") {
+                simple_selector.attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::ExactValueMatch;
+                attribute_index++;
+            }
+
+            if (attribute_parts.at(attribute_index) == " ~") {
+                simple_selector.attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::Contains;
+                attribute_index += 2;
+            }
+
+            if (attribute_parts.at(attribute_index) == " |") {
+                simple_selector.attribute_match_type = CSS::Selector::SimpleSelector::AttributeMatchType::StartsWith;
+                attribute_index += 2;
+            }
+
+            simple_selector.attribute_value = attribute_parts.at(attribute_index);
+            return simple_selector;
+        }
+
+        if (currentToken == ":") {
+            bool is_pseudo = false;
+            index++;
+
+            if (index >= parts.size()) {
+                return {};
+            }
+
+            currentToken = parts.at(index);
+            if (currentToken == ":") {
+                is_pseudo = true;
+                index++;
+            }
+
+            if (index >= parts.size()) {
+                return {};
+            }
+
+            currentToken = parts.at(index);
+            auto pseudo_name = currentToken;
+            index++;
+
+            // Ignore for now, otherwise we produce a "false positive" selector
+            // and apply styles to the element itself, not its pseudo element
+            if (is_pseudo) {
+                return {};
+            }
+
+            if (pseudo_name.equals_ignoring_case("link")) {
+                simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Link;
+            } else if (pseudo_name.equals_ignoring_case("visited")) {
+                simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Visited;
+            } else if (pseudo_name.equals_ignoring_case("hover")) {
+                simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Hover;
+            } else if (pseudo_name.equals_ignoring_case("focus")) {
+                simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Focus;
+            } else if (pseudo_name.equals_ignoring_case("first-child")) {
+                simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::FirstChild;
+            } else if (pseudo_name.equals_ignoring_case("last-child")) {
+                simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::LastChild;
+            } else if (pseudo_name.equals_ignoring_case("only-child")) {
+                simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::OnlyChild;
+            } else if (pseudo_name.equals_ignoring_case("empty")) {
+                simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Empty;
+            } else if (pseudo_name.equals_ignoring_case("root")) {
+                simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::Root;
+            } else if (pseudo_name.equals_ignoring_case("first-of-type")) {
+                simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::FirstOfType;
+            } else if (pseudo_name.equals_ignoring_case("last-of-type")) {
+                simple_selector.pseudo_class = CSS::Selector::SimpleSelector::PseudoClass::LastOfType;
+            } else if (pseudo_name.equals_ignoring_case("before")) {
+                simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::Before;
+            } else if (pseudo_name.equals_ignoring_case("after")) {
+                simple_selector.pseudo_element = CSS::Selector::SimpleSelector::PseudoElement::After;
+            } else {
+                dbgln("Unknown pseudo class: '{}'", pseudo_name);
+                return simple_selector;
+            }
+        }
+
+        return simple_selector;
+    };
+
+    auto parse_complex_selector = [&]() -> Optional<CSS::Selector::ComplexSelector> {
+        auto relation = CSS::Selector::ComplexSelector::Relation::Descendant;
+
+        auto currentToken = parts.at(index);
+        if (is_combinator(currentToken)) {
+            if (currentToken == ">") {
+                relation = CSS::Selector::ComplexSelector::Relation::ImmediateChild;
+            }
+            if (currentToken == "+") {
+                relation = CSS::Selector::ComplexSelector::Relation::AdjacentSibling;
+            }
+            if (currentToken == "~") {
+                relation = CSS::Selector::ComplexSelector::Relation::GeneralSibling;
+            }
+            if (currentToken == "||") {
+                relation = CSS::Selector::ComplexSelector::Relation::Column;
+            }
+            index++;
+        }
+
+        Vector<CSS::Selector::SimpleSelector> simple_selectors;
+
+        for (;;) {
+            auto component = parse_simple_selector();
+            if (!component.has_value()) {
+                break;
+            }
+            simple_selectors.append(component.value());
+        }
+
+        if (simple_selectors.is_empty())
+            return {};
+
+        return CSS::Selector::ComplexSelector { relation, move(simple_selectors) };
+    };
+
+    for (;;) {
+        auto complex = parse_complex_selector();
+        if (complex.has_value()) {
+            selectors.append(complex.value());
+        }
+
+        if (index >= parts.size()) {
+            break;
+        }
+
+        auto currentToken = parts.at(index);
+        if (currentToken != ",") {
+            break;
+        }
+
+        index++;
+    }
+
+    if (selectors.is_empty()) {
+        return {};
+    }
+
+    selectors.first().relation = CSS::Selector::ComplexSelector::Relation::None;
+
     return selectors;
     return selectors;
 }
 }
 
 
@@ -100,6 +318,11 @@ void Parser::reconsume_current_input_token()
     --m_iterator_offset;
     --m_iterator_offset;
 }
 }
 
 
+bool Parser::is_combinator(String input)
+{
+    return input == ">" || input == "+" || input == "~" || input == "||";
+}
+
 Vector<QualifiedStyleRule> Parser::consume_a_list_of_rules(bool top_level)
 Vector<QualifiedStyleRule> Parser::consume_a_list_of_rules(bool top_level)
 {
 {
     Vector<QualifiedStyleRule> rules;
     Vector<QualifiedStyleRule> rules;

+ 1 - 0
Userland/Libraries/LibWeb/CSS/Parser/Parser.h

@@ -68,6 +68,7 @@ private:
     Token peek_token();
     Token peek_token();
     Token current_token();
     Token current_token();
     void reconsume_current_input_token();
     void reconsume_current_input_token();
+    bool is_combinator(String);
 
 
     Vector<QualifiedStyleRule> consume_a_list_of_rules(bool top_level);
     Vector<QualifiedStyleRule> consume_a_list_of_rules(bool top_level);
     AtStyleRule consume_an_at_rule();
     AtStyleRule consume_an_at_rule();

+ 1 - 1
Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp

@@ -161,7 +161,7 @@ String StyleFunctionRule::to_string() const
     builder.append(m_name);
     builder.append(m_name);
     builder.append("(");
     builder.append("(");
     append_raw(builder, ", ", m_values);
     append_raw(builder, ", ", m_values);
-    builder.append(");");
+    builder.append(")");
 
 
     return builder.to_string();
     return builder.to_string();
 }
 }

+ 2 - 0
Userland/Libraries/LibWeb/CSS/Selector.h

@@ -53,6 +53,7 @@ public:
             HasAttribute,
             HasAttribute,
             ExactValueMatch,
             ExactValueMatch,
             Contains,
             Contains,
+            StartsWith,
         };
         };
 
 
         AttributeMatchType attribute_match_type { AttributeMatchType::None };
         AttributeMatchType attribute_match_type { AttributeMatchType::None };
@@ -67,6 +68,7 @@ public:
             Descendant,
             Descendant,
             AdjacentSibling,
             AdjacentSibling,
             GeneralSibling,
             GeneralSibling,
+            Column,
         };
         };
         Relation relation { Relation::None };
         Relation relation { Relation::None };
 
 

+ 2 - 0
Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp

@@ -151,6 +151,8 @@ static bool matches(const CSS::Selector& selector, int component_list_index, con
                 return true;
                 return true;
         }
         }
         return false;
         return false;
+    case CSS::Selector::ComplexSelector::Relation::Column:
+        TODO();
     }
     }
     VERIFY_NOT_REACHED();
     VERIFY_NOT_REACHED();
 }
 }

+ 6 - 0
Userland/Libraries/LibWeb/Dump.cpp

@@ -285,6 +285,9 @@ void dump_selector(StringBuilder& builder, const CSS::Selector& selector)
         case CSS::Selector::ComplexSelector::Relation::GeneralSibling:
         case CSS::Selector::ComplexSelector::Relation::GeneralSibling:
             relation_description = "GeneralSibling";
             relation_description = "GeneralSibling";
             break;
             break;
+        case CSS::Selector::ComplexSelector::Relation::Column:
+            relation_description = "Column";
+            break;
         }
         }
 
 
         if (*relation_description)
         if (*relation_description)
@@ -323,6 +326,9 @@ void dump_selector(StringBuilder& builder, const CSS::Selector& selector)
             case CSS::Selector::SimpleSelector::AttributeMatchType::Contains:
             case CSS::Selector::SimpleSelector::AttributeMatchType::Contains:
                 attribute_match_type_description = "Contains";
                 attribute_match_type_description = "Contains";
                 break;
                 break;
+            case CSS::Selector::SimpleSelector::AttributeMatchType::StartsWith:
+                attribute_match_type_description = "StartsWith";
+                break;
             }
             }
 
 
             const char* pseudo_class_description = "";
             const char* pseudo_class_description = "";