srctree

Robin Linden parent 92762f85 bdbe2146
css: Skip over nested CSS rules instead of letting them break things

inlinesplit
css/parser.cpp added: 48, removed: 12, total 36
@@ -412,7 +412,29 @@ std::optional<css::Rule> Parser::parse_rule() {
skip_whitespace_and_comments();
 
while (peek() != '}') {
auto decl = parse_declaration();
// TODO(robinlinden): This doesn't get along with nested rules like
// `foo // { bar:baz { font-size: 3em; } }`
// due to the assumption that "ascii:" always is a CSS property name.
auto nested_rule_or_declaration_name = consume_while([](char c) { return c != ':' && c != '{'; });
if (!nested_rule_or_declaration_name || nested_rule_or_declaration_name->empty()) {
return std::nullopt;
}
 
if (peek() == '{'
|| (!util::is_alpha(nested_rule_or_declaration_name->front())
&& nested_rule_or_declaration_name->front() != '-')) {
// TODO(robinlinden): Nested rule. Skip over it for now.
pos_ -= nested_rule_or_declaration_name->size();
if (auto nested_rule = parse_rule()) {
spdlog::warn("Ignoring nested rule: '{}'", to_string(*nested_rule));
} else {
spdlog::warn("Unable to parse nested rule: '{}'", *nested_rule_or_declaration_name);
}
skip_whitespace_and_comments();
continue;
}
 
auto decl = parse_declaration(*nested_rule_or_declaration_name);
if (!decl) {
return std::nullopt;
}
@@ -434,12 +456,7 @@ std::optional<css::Rule> Parser::parse_rule() {
return rule;
}
 
std::optional<std::pair<std::string_view, std::string_view>> Parser::parse_declaration() {
auto name = consume_while([](char c) { return c != ':'; });
if (!name) {
return std::nullopt;
}
 
std::optional<std::pair<std::string_view, std::string_view>> Parser::parse_declaration(std::string_view name) {
consume_char(); // :
skip_whitespace_and_comments();
auto value = consume_while([](char c) { return c != ';' && c != '}'; });
@@ -448,7 +465,7 @@ std::optional<std::pair<std::string_view, std::string_view>> Parser::parse_decla
}
 
skip_if_neq('}'); // ;
return std::pair{*name, *value};
return std::pair{name, *value};
}
 
void Parser::add_declaration(Declarations &declarations, std::string_view name, std::string_view value) {
 
css/parser.h added: 48, removed: 12, total 36
@@ -52,7 +52,7 @@ private:
void skip_whitespace_and_comments();
 
std::optional<css::Rule> parse_rule();
std::optional<std::pair<std::string_view, std::string_view>> parse_declaration();
std::optional<std::pair<std::string_view, std::string_view>> parse_declaration(std::string_view name);
 
static void add_declaration(Declarations &, std::string_view name, std::string_view value);
 
 
css/parser_test.cpp added: 48, removed: 12, total 36
@@ -1224,5 +1224,24 @@ int main() {
css::Rule{.selectors = {{"p"}}, .custom_properties = {{"--var", "value"}}});
});
 
// TODO(robinlinden): Nested rules are currently skipped, but at least
// they mostly don't break parsing of the rule they're nested in.
etest::test("parser: nested rule", [] {
expect_eq(css::parse("p { color: green; a { font-size: 3px; } font-size: 5px; }").rules, //
std::vector{
css::Rule{
.selectors = {{"p"}},
.declarations{
{css::PropertyId::Color, "green"},
{css::PropertyId::FontSize, "5px"},
},
},
});
});
 
etest::test("parser: eof in nested rule", [] {
expect(css::parse("p { color: green; a { font-size: 3px; ").rules.empty()); //
});
 
return etest::run_all_tests();
}