summaryrefslogtreecommitdiff
path: root/Userland
diff options
context:
space:
mode:
authorstelar7 <dudedbz@gmail.com>2021-04-29 21:16:28 +0200
committerAndreas Kling <kling@serenityos.org>2021-04-30 08:52:36 +0200
commit55446172cb7ecfc3b6183bbf09d06dd10a56c8fa (patch)
treea5cf8181eebe8dd655011bb24ed35776e87204a8 /Userland
parent5acac54085ec7530b7dbd25ae4a53c901d9ccef7 (diff)
downloadserenity-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.cpp225
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/Parser.h1
-rw-r--r--Userland/Libraries/LibWeb/CSS/Parser/StyleRules.cpp2
-rw-r--r--Userland/Libraries/LibWeb/CSS/Selector.h2
-rw-r--r--Userland/Libraries/LibWeb/CSS/SelectorEngine.cpp2
-rw-r--r--Userland/Libraries/LibWeb/Dump.cpp6
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 = "";