From ee72dcd52327f06084f9683dd8c0c1a1b7a7f5cf Mon Sep 17 00:00:00 2001 From: MacDue Date: Sun, 30 Oct 2022 19:23:00 +0100 Subject: [PATCH] LibWeb: Parse CSS ``s This parses according to the following grammer: = [ [ left | center | right ] || [ top | center | bottom ] | [ left | center | right | ] [ top | center | bottom | ]? | [ [ left | right ] ] && [ [ top | bottom ] ] ] The code is a little hairy and simply tries each alternative in turn, manually checking the possible orders. There may be a better way to represent this. --- .../Libraries/LibWeb/CSS/Parser/Parser.cpp | 206 ++++++++++++++++++ Userland/Libraries/LibWeb/CSS/Parser/Parser.h | 1 + 2 files changed, 207 insertions(+) diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp index 4bb4b47405..ea184fd9d9 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp @@ -2518,6 +2518,212 @@ RefPtr Parser::parse_linear_gradient_function(ComponentValue const& return LinearGradientStyleValue::create(gradient_direction, move(color_stops), gradient_type, repeating_gradient); } +Optional Parser::parse_position(TokenStream& tokens) +{ + auto transaction = tokens.begin_transaction(); + tokens.skip_whitespace(); + if (!tokens.has_next_token()) + return {}; + + auto parse_horizontal_preset = [&](auto ident) -> Optional { + if (ident.equals_ignoring_case("left"sv)) + return PositionValue::HorizontalPreset::Left; + if (ident.equals_ignoring_case("center"sv)) + return PositionValue::HorizontalPreset::Center; + if (ident.equals_ignoring_case("right"sv)) + return PositionValue::HorizontalPreset::Right; + return {}; + }; + + auto parse_vertical_preset = [&](auto ident) -> Optional { + if (ident.equals_ignoring_case("top"sv)) + return PositionValue::VerticalPreset::Top; + if (ident.equals_ignoring_case("center"sv)) + return PositionValue::VerticalPreset::Center; + if (ident.equals_ignoring_case("bottom"sv)) + return PositionValue::VerticalPreset::Bottom; + return {}; + }; + + auto parse_horizontal_edge = [&](auto ident) -> Optional { + if (ident.equals_ignoring_case("left"sv)) + return PositionValue::HorizontalEdge::Left; + if (ident.equals_ignoring_case("right"sv)) + return PositionValue::HorizontalEdge::Right; + return {}; + }; + + auto parse_vertical_edge = [&](auto ident) -> Optional { + if (ident.equals_ignoring_case("top"sv)) + return PositionValue::VerticalEdge::Top; + if (ident.equals_ignoring_case("bottom"sv)) + return PositionValue::VerticalEdge::Bottom; + return {}; + }; + + // = [ + // [ left | center | right ] || [ top | center | bottom ] + // | + // [ left | center | right | ] + // [ top | center | bottom | ]? + // | + // [ [ left | right ] ] && + // [ [ top | bottom ] ] + // ] + + // [ left | center | right ] || [ top | center | bottom ] + auto alternation_1 = [&]() -> Optional { + auto transaction = tokens.begin_transaction(); + PositionValue position {}; + auto& first_token = tokens.next_token(); + if (!first_token.is(Token::Type::Ident)) + return {}; + auto ident = first_token.token().ident(); + // ? + auto horizontal_position = parse_horizontal_preset(ident); + if (horizontal_position.has_value()) { + position.horizontal_position = *horizontal_position; + tokens.skip_whitespace(); + if (tokens.has_next_token()) { + auto& second_token = tokens.next_token(); + if (!second_token.is(Token::Type::Ident)) + return {}; + auto vertical_position = parse_vertical_preset(second_token.token().ident()); + if (!vertical_position.has_value()) + return {}; + position.vertical_position = *vertical_position; + } + } else { + // ? + auto vertical_position = parse_vertical_preset(ident); + if (!vertical_position.has_value()) + return {}; + position.vertical_position = *vertical_position; + if (tokens.has_next_token()) { + auto& second_token = tokens.next_token(); + if (!second_token.is(Token::Type::Ident)) + return {}; + auto horizontal_position = parse_horizontal_preset(second_token.token().ident()); + if (!horizontal_position.has_value()) + return {}; + position.horizontal_position = *horizontal_position; + } + } + transaction.commit(); + return position; + }; + + // [ left | center | right | ] + // [ top | center | bottom | ]? + auto alternation_2 = [&]() -> Optional { + auto transaction = tokens.begin_transaction(); + PositionValue position {}; + auto& first_token = tokens.next_token(); + if (first_token.is(Token::Type::Ident)) { + auto horizontal_position = parse_horizontal_preset(first_token.token().ident()); + if (!horizontal_position.has_value()) + return {}; + position.horizontal_position = *horizontal_position; + } else { + auto dimension = parse_dimension(first_token); + if (!dimension.has_value() || !dimension->is_length_percentage()) + return {}; + position.horizontal_position = dimension->length_percentage(); + } + tokens.skip_whitespace(); + if (tokens.has_next_token()) { + auto& second_token = tokens.next_token(); + if (second_token.is(Token::Type::Ident)) { + auto vertical_position = parse_vertical_preset(second_token.token().ident()); + if (!vertical_position.has_value()) + return {}; + position.vertical_position = *vertical_position; + } else { + auto dimension = parse_dimension(second_token); + if (!dimension.has_value() || !dimension->is_length_percentage()) + return {}; + position.vertical_position = dimension->length_percentage(); + } + } + transaction.commit(); + return position; + }; + + // [ [ left | right ] ] && + // [ [ top | bottom ] ] + auto alternation_3 = [&]() -> Optional { + auto transaction = tokens.begin_transaction(); + PositionValue position {}; + + auto parse_horizontal = [&] { + // [ left | right ] ] + auto transaction = tokens.begin_transaction(); + tokens.skip_whitespace(); + if (!tokens.has_next_token()) + return false; + auto& first_token = tokens.next_token(); + if (!first_token.is(Token::Type::Ident)) + return false; + auto horizontal_egde = parse_horizontal_edge(first_token.token().ident()); + if (!horizontal_egde.has_value()) + return false; + position.x_relative_to = *horizontal_egde; + tokens.skip_whitespace(); + if (!tokens.has_next_token()) + return false; + auto& second_token = tokens.next_token(); + auto dimension = parse_dimension(second_token); + if (!dimension.has_value() || !dimension->is_length_percentage()) + return false; + position.horizontal_position = dimension->length_percentage(); + transaction.commit(); + return true; + }; + + auto parse_vertical = [&] { + // [ top | bottom ] ] + auto transaction = tokens.begin_transaction(); + tokens.skip_whitespace(); + if (!tokens.has_next_token()) + return false; + auto& first_token = tokens.next_token(); + if (!first_token.is(Token::Type::Ident)) + return false; + auto vertical_edge = parse_vertical_edge(first_token.token().ident()); + if (!vertical_edge.has_value()) + return false; + position.y_relative_to = *vertical_edge; + tokens.skip_whitespace(); + if (!tokens.has_next_token()) + return false; + auto& second_token = tokens.next_token(); + auto dimension = parse_dimension(second_token); + if (!dimension.has_value() || !dimension->is_length_percentage()) + return false; + position.vertical_position = dimension->length_percentage(); + transaction.commit(); + return true; + }; + + if ((parse_horizontal() && parse_vertical()) || (parse_vertical() && parse_horizontal())) { + transaction.commit(); + return position; + } + + return {}; + }; + + // Note: The alternatives must be attempted in this order since `alternation_2' can match a prefix of `alternation_3' + auto position = alternation_3(); + if (!position.has_value()) + position = alternation_2(); + if (!position.has_value()) + position = alternation_1(); + if (position.has_value()) + transaction.commit(); + return position; +} + CSSRule* Parser::convert_to_rule(NonnullRefPtr rule) { if (rule->is_at_rule()) { diff --git a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h index 7d31d8ad93..7ba16298f0 100644 --- a/Userland/Libraries/LibWeb/CSS/Parser/Parser.h +++ b/Userland/Libraries/LibWeb/CSS/Parser/Parser.h @@ -254,6 +254,7 @@ private: Optional parse_min_max(Vector const&); Optional parse_repeat(Vector const&); Optional parse_track_sizing_function(ComponentValue const&); + Optional parse_position(TokenStream&); enum class AllowedDataUrlType { None,