Skip to content

Commit

Permalink
style: Support correctly :first-child, :last-child, and > *.
Browse files Browse the repository at this point in the history
  • Loading branch information
Louciole committed Nov 27, 2024
1 parent 68b3ee3 commit 31aeb77
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/vaev-driver/res/user-agent.css
Original file line number Diff line number Diff line change
Expand Up @@ -215,4 +215,5 @@ tr {
td,
th {
display: table-cell;
font-weight: bold;
}
53 changes: 49 additions & 4 deletions src/vaev-style/select.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,37 @@ static bool _matchLink(Markup::Element const &el) {
return el.tagName == Html::A and el.hasAttribute(Html::HREF_ATTR);
}

// 14.3.3. :first-child pseudo-class
// https://www.w3.org/TR/selectors-4/#the-first-child-pseudo

static bool _matchFirstChild(Markup::Element const &e) {
Cursor<Markup::Node> curr = &e;
while (curr->hasPreviousSibling()) {
auto prev = curr->previousSibling();
if (auto el = prev.is<Markup::Element>())
return false;
curr = &prev.unwrap();
}
return true;
}

// 14.3.4. :last-child pseudo-class
// https://www.w3.org/TR/selectors-4/#the-last-child-pseudo

static bool _matchLastChild(Markup::Element const &e) {
// yap("coucouuuuuu {}", e);
Cursor<Markup::Node> curr = &e;
while (curr->hasNextSibling()) {
auto next = curr->nextSibling();
// yap("next: {}", next);
if (auto el = next.is<Markup::Element>())
return false;
curr = &next.unwrap();
}
// yap("bruh");
return true;
}

// 14.4.3. :first-of-type pseudo-class
// https://www.w3.org/TR/selectors-4/#the-first-of-type-pseudo

Expand Down Expand Up @@ -225,6 +256,12 @@ static bool _match(Pseudo const &s, Markup::Element const &el) {
case Pseudo::LAST_OF_TYPE:
return _matchLastOfType(el);

case Pseudo::FIRST_CHILD:
return _matchFirstChild(el);

case Pseudo::LAST_CHILD:
return _matchLastChild(el);

default:
logDebugIf(DEBUG_SELECTORS, "unimplemented pseudo class: {}", s);
return false;
Expand Down Expand Up @@ -355,7 +392,8 @@ static OpCode _peekOpCode(Cursor<Css::Sst> &cur) {
cur.peek() == Css::Token::IDENT or
cur.peek() == Css::Token::HASH or
cur.peek().token.data == "." or
cur.peek().token.data == "*"
cur.peek().token.data == "*" or
cur.peek().token.data == ":"
) {
return OpCode::DESCENDANT;
} else {
Expand Down Expand Up @@ -409,7 +447,7 @@ static Selector _parseInfixExpr(Selector lhs, Cursor<Css::Sst> &cur, OpCode opCo

static Selector _parseSelectorElement(Cursor<Css::Sst> &cur, OpCode currentOp) {
if (cur.ended()) {
// logError("ERROR : unterminated selector");
logErrorIf(DEBUG_SELECTORS, "ERROR : unterminated selector");
return EmptySelector{};
}
Selector val;
Expand Down Expand Up @@ -438,11 +476,17 @@ static Selector _parseSelectorElement(Cursor<Css::Sst> &cur, OpCode currentOp) {
if (cur->token.type == Css::Token::COLON) {
cur.next();
if (cur.ended()) {
// logError("ERROR : unterminated selector");
logErrorIf(DEBUG_SELECTORS, "ERROR : unterminated selector");
return EmptySelector{};
}
}
val = Pseudo::make(cur->token.data);

if (cur->prefix == Css::Token::function("not(")) {
Cursor<Css::Sst> c = cur->content;
val = Selector::not_(_parseSelectorElement(c, OpCode::NOT));
} else {
val = Pseudo::make(cur->token.data);
}
break;
default:
val = ClassSelector{cur->token.data};
Expand Down Expand Up @@ -551,6 +595,7 @@ Selector Selector::parse(Cursor<Css::Sst> &c) {
return EmptySelector{};
}

logDebugIf(DEBUG_SELECTORS, "PARSING SELECTOR : {}", c);
Selector currentSelector = _parseSelectorElement(c, OpCode::NOP);

while (not c.ended()) {
Expand Down
57 changes: 57 additions & 0 deletions src/vaev-style/tests/test-parse-selectors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ test$("vaev-style-parse-infix-selectors") {
)
);

expectEq$(
Selector::parse(":not(.className)"),
Selector::not_(ClassSelector{"className"s})
);

return Ok();
}

Expand Down Expand Up @@ -167,6 +172,40 @@ test$("vaev-style-parse-mixed-selectors") {
)
);

expectEq$(
Selector::parse(":not(:first-child)"),
Selector::not_(Pseudo{Pseudo::FIRST_CHILD})
);

expectEq$(
Selector::parse("tr:not(:last-child) th:not(:first-child)"),
Selector::descendant(
Selector::and_({TypeSelector{Html::TR}, Selector::not_(Pseudo{Pseudo::LAST_CHILD})}),
Selector::and_({TypeSelector{Html::TH}, {Selector::not_(Pseudo{Pseudo::FIRST_CHILD})}})
)
);

expectEq$(
Selector::parse(".o_content .o_table > thead > tr:not(:last-child) th:not(:first-child)"),
Selector::descendant(
Selector::child(
Selector::descendant(
ClassSelector{"o_content"s},
Selector::child(ClassSelector{"o_table"s}, TypeSelector{Html::THEAD})
),
Selector::and_({TypeSelector{Html::TR}, Selector::not_(Pseudo{Pseudo::LAST_CHILD})})
),
Selector::and_({TypeSelector{Html::TH}, {Selector::not_(Pseudo{Pseudo::FIRST_CHILD})}})
)
);

expectEq$(
Selector::parse(".o_content .o_table thead, .o_content .o_table tbody, .o_content .o_table tfoot, .o_content .o_table tr, .o_content .o_table td, .o_content .o_table th "),
Selector::descendant(
Selector::and_({TypeSelector{Html::TR}, Selector::not_(Pseudo{Pseudo::LAST_CHILD})}),
Selector::and_({TypeSelector{Html::TH}, {Selector::not_(Pseudo{Pseudo::FIRST_CHILD})}})
)
);
return Ok();
}

Expand All @@ -186,6 +225,24 @@ test$("vaev-style-parse-pseudo-selectors") {
Pseudo{Pseudo::make("root")}
);

expectEq$(
Selector::parse(":first-child"),
Pseudo{Pseudo::FIRST_CHILD}
);

expectEq$(
Selector::parse(":last-child"),
Pseudo{Pseudo::LAST_CHILD}
);

expectEq$(
Selector::parse(".class :last-child"),
Selector::descendant(
ClassSelector{"class"s},
Pseudo{Pseudo::LAST_CHILD}
)
);

expectEq$(
Selector::parse("html:hover"),
Selector::and_({
Expand Down

0 comments on commit 31aeb77

Please sign in to comment.