mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 19:12:43 +00:00 
			
		
		
		
	LibWeb: Add selector support to the new CSSParser
This is stolen from the old parser, but it seems to parse fine :^)
This commit is contained in:
		
							parent
							
								
									5acac54085
								
							
						
					
					
						commit
						55446172cb
					
				
					 6 changed files with 236 additions and 2 deletions
				
			
		|  | @ -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; | ||||
|  |  | |||
|  | @ -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(); | ||||
|  |  | |||
|  | @ -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(); | ||||
| } | ||||
|  |  | |||
|  | @ -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 }; | ||||
| 
 | ||||
|  |  | |||
|  | @ -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(); | ||||
| } | ||||
|  |  | |||
|  | @ -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 = ""; | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 stelar7
						stelar7