1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-05-31 10:48:11 +00:00

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.
This commit is contained in:
MacDue 2022-10-30 19:23:00 +01:00 committed by Linus Groh
parent e568c93404
commit ee72dcd523
2 changed files with 207 additions and 0 deletions

View file

@ -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);
}
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)
{
if (rule->is_at_rule()) {