Преглед на файлове

LibWeb: Parse CSS `<position>`s

This parses <position> according to the following grammer:

<position> = [
  [ left | center | right ] || [ top | center | bottom ]
|
  [ left | center | right | <length-percentage> ]
  [ top | center | bottom | <length-percentage> ]?
|
  [ [ left | right ] <length-percentage> ] &&
  [ [ top | bottom ] <length-percentage> ]
]

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.
MacDue преди 2 години
родител
ревизия
ee72dcd523
променени са 2 файла, в които са добавени 207 реда и са изтрити 0 реда
  1. 206 0
      Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp
  2. 1 0
      Userland/Libraries/LibWeb/CSS/Parser/Parser.h

+ 206 - 0
Userland/Libraries/LibWeb/CSS/Parser/Parser.cpp

@@ -2518,6 +2518,212 @@ RefPtr<StyleValue> Parser::parse_linear_gradient_function(ComponentValue const&
     return LinearGradientStyleValue::create(gradient_direction, move(color_stops), gradient_type, repeating_gradient);
     return LinearGradientStyleValue::create(gradient_direction, move(color_stops), gradient_type, repeating_gradient);
 }
 }
 
 
+Optional<PositionValue> Parser::parse_position(TokenStream<ComponentValue>& tokens)
+{
+    auto transaction = tokens.begin_transaction();
+    tokens.skip_whitespace();
+    if (!tokens.has_next_token())
+        return {};
+
+    auto parse_horizontal_preset = [&](auto ident) -> Optional<PositionValue::HorizontalPreset> {
+        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<PositionValue::VerticalPreset> {
+        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<PositionValue::HorizontalEdge> {
+        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<PositionValue::VerticalEdge> {
+        if (ident.equals_ignoring_case("top"sv))
+            return PositionValue::VerticalEdge::Top;
+        if (ident.equals_ignoring_case("bottom"sv))
+            return PositionValue::VerticalEdge::Bottom;
+        return {};
+    };
+
+    // <position> = [
+    //   [ left | center | right ] || [ top | center | bottom ]
+    // |
+    //   [ left | center | right | <length-percentage> ]
+    //   [ top | center | bottom | <length-percentage> ]?
+    // |
+    //   [ [ left | right ] <length-percentage> ] &&
+    //   [ [ top | bottom ] <length-percentage> ]
+    // ]
+
+    // [ left | center | right ] || [ top | center | bottom ]
+    auto alternation_1 = [&]() -> Optional<PositionValue> {
+        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();
+        // <horizontal-position> <vertical-position>?
+        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 {
+            // <vertical-position> <horizontal-position>?
+            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 | <length-percentage> ]
+    // [ top | center | bottom | <length-percentage> ]?
+    auto alternation_2 = [&]() -> Optional<PositionValue> {
+        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 ] <length-percentage> ] &&
+    // [ [ top | bottom ] <length-percentage> ]
+    auto alternation_3 = [&]() -> Optional<PositionValue> {
+        auto transaction = tokens.begin_transaction();
+        PositionValue position {};
+
+        auto parse_horizontal = [&] {
+            // [ left | right ] <length-percentage> ]
+            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 ] <length-percentage> ]
+            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> rule)
 CSSRule* Parser::convert_to_rule(NonnullRefPtr<Rule> rule)
 {
 {
     if (rule->is_at_rule()) {
     if (rule->is_at_rule()) {

+ 1 - 0
Userland/Libraries/LibWeb/CSS/Parser/Parser.h

@@ -254,6 +254,7 @@ private:
     Optional<GridMinMax> parse_min_max(Vector<ComponentValue> const&);
     Optional<GridMinMax> parse_min_max(Vector<ComponentValue> const&);
     Optional<GridRepeat> parse_repeat(Vector<ComponentValue> const&);
     Optional<GridRepeat> parse_repeat(Vector<ComponentValue> const&);
     Optional<ExplicitGridTrack> parse_track_sizing_function(ComponentValue const&);
     Optional<ExplicitGridTrack> parse_track_sizing_function(ComponentValue const&);
+    Optional<PositionValue> parse_position(TokenStream<ComponentValue>&);
 
 
     enum class AllowedDataUrlType {
     enum class AllowedDataUrlType {
         None,
         None,