/* * Copyright (c) 2025, Sam Atkins * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include namespace Web::CSS::Parser { Parser::ParseErrorOr> Parser::parse_descriptor_value(AtRuleID at_rule_id, DescriptorID descriptor_id, TokenStream& unprocessed_tokens) { if (!at_rule_supports_descriptor(at_rule_id, descriptor_id)) { dbgln_if(CSS_PARSER_DEBUG, "Unsupported descriptor '{}' in '{}'", to_string(descriptor_id), to_string(at_rule_id)); return ParseError::SyntaxError; } auto context_guard = push_temporary_value_parsing_context(DescriptorContext { at_rule_id, descriptor_id }); Vector component_values; while (unprocessed_tokens.has_next_token()) { if (unprocessed_tokens.peek_token().is(Token::Type::Semicolon)) break; // FIXME: Stop removing whitespace here. It's just for compatibility with the property-parsing code. auto const& token = unprocessed_tokens.consume_a_token(); if (token.is(Token::Type::Whitespace)) continue; component_values.append(token); } TokenStream tokens { component_values }; auto metadata = get_descriptor_metadata(at_rule_id, descriptor_id); for (auto const& option : metadata.syntax) { auto transaction = tokens.begin_transaction(); auto parsed_style_value = option.visit( [&](Keyword keyword) { return parse_all_as_single_keyword_value(tokens, keyword); }, [&](PropertyID property_id) -> RefPtr { auto value_or_error = parse_css_value(property_id, tokens); if (value_or_error.is_error()) return nullptr; auto value_for_property = value_or_error.release_value(); // Descriptors don't accept the following, which properties do: // - CSS-wide keywords // - Shorthands // - Arbitrary substitution functions (so, UnresolvedStyleValue) if (value_for_property->is_css_wide_keyword() || value_for_property->is_shorthand() || value_for_property->is_unresolved()) return nullptr; return value_for_property; }, [&](DescriptorMetadata::ValueType value_type) -> RefPtr { switch (value_type) { case DescriptorMetadata::ValueType::FamilyName: return parse_family_name_value(tokens); case DescriptorMetadata::ValueType::FontSrcList: { // "If a component value is parsed correctly and is of a font format or font tech that the UA // supports, add it to the list of supported sources. If parsing a component value results in a // parsing error or its format or tech are unsupported, do not add it to the list of supported // sources. // If there are no supported entries at the end of this process, the value for the src descriptor // is a parse error. // These parsing rules allow for graceful fallback of fonts for user agents which don’t support a // particular font tech or font format." // https://drafts.csswg.org/css-fonts-4/#font-face-src-parsing auto source_lists = parse_a_comma_separated_list_of_component_values(tokens); StyleValueVector valid_sources; for (auto const& source_list : source_lists) { TokenStream source_tokens { source_list }; if (auto font_source = parse_font_source_value(source_tokens); font_source && !source_tokens.has_next_token()) valid_sources.append(font_source.release_nonnull()); } if (valid_sources.is_empty()) return nullptr; return StyleValueList::create(move(valid_sources), StyleValueList::Separator::Comma); } case DescriptorMetadata::ValueType::OptionalDeclarationValue: // `component_values` already has what we want. Just skip through its tokens so code below knows we consumed them. while (tokens.has_next_token()) tokens.discard_a_token(); return UnresolvedStyleValue::create(move(component_values), false, {}); case DescriptorMetadata::ValueType::PositivePercentage: if (auto percentage_value = parse_percentage_value(tokens)) { if (percentage_value->is_percentage()) { if (percentage_value->as_percentage().value() < 0) return nullptr; return percentage_value.release_nonnull(); } // All calculations in descriptors must be resolvable at parse-time. if (percentage_value->is_calculated()) { auto percentage = percentage_value->as_calculated().resolve_percentage({}); if (percentage.has_value() && percentage->value() >= 0) return PercentageStyleValue::create(percentage.release_value()); return nullptr; } } return nullptr; case DescriptorMetadata::ValueType::String: return parse_string_value(tokens); case DescriptorMetadata::ValueType::UnicodeRangeTokens: return parse_comma_separated_value_list(tokens, [this](auto& tokens) -> RefPtr { return parse_unicode_range_value(tokens); }); } return nullptr; }); if (!parsed_style_value || tokens.has_next_token()) continue; transaction.commit(); return parsed_style_value.release_nonnull(); } if constexpr (CSS_PARSER_DEBUG) { dbgln("Failed to parse descriptor '{}' in '{}'", to_string(descriptor_id), to_string(at_rule_id)); tokens.dump_all_tokens(); } return ParseError::SyntaxError; } Optional Parser::convert_to_descriptor(AtRuleID at_rule_id, Declaration const& declaration) { auto descriptor_id = descriptor_id_from_string(at_rule_id, declaration.name); if (!descriptor_id.has_value()) return {}; auto value_token_stream = TokenStream(declaration.value); auto value = parse_descriptor_value(at_rule_id, descriptor_id.value(), value_token_stream); if (value.is_error()) { if (value.error() == ParseError::SyntaxError) { if constexpr (CSS_PARSER_DEBUG) { dbgln("Unable to parse value for CSS @{} descriptor '{}'.", to_string(at_rule_id), declaration.name); value_token_stream.dump_all_tokens(); } } return {}; } return Descriptor { *descriptor_id, value.release_value() }; } }