diff options
author | stelar7 <dudedbz@gmail.com> | 2021-04-29 21:16:28 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-04-30 08:52:36 +0200 |
commit | 55446172cb7ecfc3b6183bbf09d06dd10a56c8fa (patch) | |
tree | a5cf8181eebe8dd655011bb24ed35776e87204a8 /Userland | |
parent | 5acac54085ec7530b7dbd25ae4a53c901d9ccef7 (diff) | |
download | serenity-55446172cb7ecfc3b6183bbf09d06dd10a56c8fa.zip |
LibWeb: Add selector support to the new CSSParser
This is stolen from the old parser, but it seems to parse fine :^)
Diffstat (limited to 'Userland')
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp | 225 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/Selector.h | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibWeb/Dump.cpp | 6 |
6 files changed, 236 insertions, 2 deletions
diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 94beb95b67..3d07d6a248 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -13,6 +13,7 @@ #include <LibWeb/CSS/Parser/StyleComponentValueRule.h> #include <LibWeb/CSS/Parser/StyleFunctionRule.h> #include <LibWeb/CSS/Selector.h> +#include <LibWeb/Dump.h> #define CSS_PARSER_TRACE 1 @@ -76,6 +77,8 @@ Vector<QualifiedStyleRule> Parser::parse_as_stylesheet() dbgln(""); auto selectors = parse_selectors(rule.m_prelude); + CSS::Selector selector = Selector(move(selectors)); + dump_selector(selector); } return rules; @@ -83,8 +86,223 @@ Vector<QualifiedStyleRule> Parser::parse_as_stylesheet() 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; + + 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; } @@ -100,6 +318,11 @@ void Parser::reconsume_current_input_token() --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> rules; diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 94c8465141..a41fb33bf4 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -68,6 +68,7 @@ private: Token peek_token(); Token current_token(); void reconsume_current_input_token(); + bool is_combinator(String); Vector<QualifiedStyleRule> consume_a_list_of_rules(bool top_level); AtStyleRule consume_an_at_rule(); diff --git a/Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp b/Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp index 10841835fa..3794360613 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp @@ -161,7 +161,7 @@ String StyleFunctionRule::to_string() const builder.append(m_name); builder.append("("); append_raw(builder, ", ", m_values); - builder.append(");"); + builder.append(")"); return builder.to_string(); } diff --git a/Userland/Libraries/LibWeb/CSS/Selector.h b/Userland/Libraries/LibWeb/CSS/Selector.h index 1247b2b0d4..62c28c4642 100644 --- a/Userland/Libraries/LibWeb/CSS/Selector.h +++ b/Userland/Libraries/LibWeb/CSS/Selector.h @@ -53,6 +53,7 @@ public: HasAttribute, ExactValueMatch, Contains, + StartsWith, }; AttributeMatchType attribute_match_type { AttributeMatchType::None }; @@ -67,6 +68,7 @@ public: Descendant, AdjacentSibling, GeneralSibling, + Column, }; Relation relation { Relation::None }; diff --git a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp b/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp index ce7a02f284..866266f914 100644 --- a/Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp +++ b/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 false; + case CSS::Selector::ComplexSelector::Relation::Column: + TODO(); } VERIFY_NOT_REACHED(); } diff --git a/Userland/Libraries/LibWeb/Dump.cpp b/Userland/Libraries/LibWeb/Dump.cpp index c73bf1a9c4..c5497d96a6 100644 --- a/Userland/Libraries/LibWeb/Dump.cpp +++ b/Userland/Libraries/LibWeb/Dump.cpp @@ -285,6 +285,9 @@ void dump_selector(StringBuilder& builder, const CSS::Selector& selector) case CSS::Selector::ComplexSelector::Relation::GeneralSibling: relation_description = "GeneralSibling"; break; + case CSS::Selector::ComplexSelector::Relation::Column: + relation_description = "Column"; + break; } if (*relation_description) @@ -323,6 +326,9 @@ void dump_selector(StringBuilder& builder, const CSS::Selector& selector) case CSS::Selector::SimpleSelector::AttributeMatchType::Contains: attribute_match_type_description = "Contains"; break; + case CSS::Selector::SimpleSelector::AttributeMatchType::StartsWith: + attribute_match_type_description = "StartsWith"; + break; } const char* pseudo_class_description = ""; |