/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ #pragma once #include #include #include #include #include #include #include namespace facebook::react { namespace detail { template class CSSValueParser { using CSSValue = CSSValueVariant; public: explicit constexpr CSSValueParser(std::string_view css) : parser_{css} {} /* * Attempts to parse the characters starting at the current component value * into one of the given data types. */ constexpr CSSValue consumeValue( CSSComponentValueDelimiter delimeter = CSSComponentValueDelimiter::None) { return parser_.consumeComponentValue( delimeter, [&](const CSSPreservedToken& token) { // CSS-global keywords if constexpr (hasType()) { if (auto cssWideKeyword = consumeCSSWideKeyword(token)) { return *cssWideKeyword; } } // Property-specific keywords if constexpr (hasType()) { if (auto keyword = consumeKeyword(token)) { return *keyword; } } // if constexpr (hasType()) { if (auto ratio = consumeRatio(token)) { return *ratio; } } // if constexpr (hasType()) { if (auto number = consumeNumber(token)) { return *number; } } // if constexpr (hasType()) { if (auto length = consumeLength(token)) { return *length; } } // if constexpr (hasType()) { if (auto angle = consumeAngle(token)) { return *angle; } } // if constexpr (hasType()) { if (auto percentage = consumePercentage(token)) { return *percentage; } } // if constexpr (hasType()) { if (auto colorValue = consumeColorToken(token)) { return *colorValue; } } return CSSValue{}; }); // TODO: support function component values and simple blocks } constexpr bool isFinished() const { return parser_.isFinished(); } constexpr void consumeWhitespace() { parser_.consumeWhitespace(); } private: template constexpr static bool hasType() { return traits::containsType(); } template constexpr static bool hasType() { return false; } constexpr std::optional consumeKeyword( const CSSPreservedToken& token) { if (token.type() == CSSTokenType::Ident) { if (auto keyword = parseCSSKeyword( token.stringValue())) { return CSSValue::keyword(*keyword); } } return {}; } constexpr std::optional consumeCSSWideKeyword( const CSSPreservedToken& token) { if (token.type() == CSSTokenType::Ident) { if (auto keyword = parseCSSKeyword(token.stringValue())) { return CSSValue::cssWideKeyword(*keyword); } } return {}; } constexpr std::optional consumeAngle( const CSSPreservedToken& token) { if (token.type() == CSSTokenType::Dimension) { if (auto unit = parseCSSAngleUnit(token.unit())) { return CSSValue::angle(canonicalize(token.numericValue(), *unit)); } } return {}; } constexpr std::optional consumePercentage( const CSSPreservedToken& token) { if (token.type() == CSSTokenType::Percentage) { return CSSValue::percentage(token.numericValue()); } return {}; } constexpr std::optional consumeNumber( const CSSPreservedToken& token) { if (token.type() == CSSTokenType::Number) { return CSSValue::number(token.numericValue()); } return {}; } constexpr std::optional consumeLength( const CSSPreservedToken& token) { switch (token.type()) { case CSSTokenType::Dimension: if (auto unit = parseCSSLengthUnit(token.unit())) { return CSSValue::length(token.numericValue(), *unit); } break; case CSSTokenType::Number: // For zero lengths the unit identifier is optional (i.e. can be // syntactically represented as the 0). However, if a 0 // could be parsed as either a or a in a // property (such as line-height), it must parse as a . // https://www.w3.org/TR/css-values-4/#lengths if (token.numericValue() == 0) { return CSSValue::length(token.numericValue(), CSSLengthUnit::Px); } break; default: break; } return {}; } constexpr std::optional consumeRatio( const CSSPreservedToken& token) { // = [ / ]? // https://www.w3.org/TR/css-values-4/#ratio if (isValidRatioPart(token.numericValue())) { float numerator = token.numericValue(); CSSSyntaxParser lookaheadParser{parser_}; auto hasSolidus = lookaheadParser.consumeComponentValue( CSSComponentValueDelimiter::Whitespace, [&](const CSSPreservedToken& token) { return token.type() == CSSTokenType::Delim && token.stringValue() == "/"; }); if (!hasSolidus) { return CSSValue::ratio(numerator, 1.0f); } // TODO: support math expression substituion for auto denominator = lookaheadParser.consumeComponentValue>( CSSComponentValueDelimiter::Whitespace, [&](const CSSPreservedToken& token) { if (token.type() == CSSTokenType::Number && isValidRatioPart(token.numericValue())) { return std::optional(token.numericValue()); } return std::optional{}; }); if (denominator.has_value()) { parser_ = lookaheadParser; return CSSValue::ratio(numerator, *denominator); } } return {}; } constexpr bool isValidRatioPart(float value) { // If either number in the is 0 or infinite, it represents a // degenerate ratio (and, generally, won’t do anything). // https://www.w3.org/TR/css-values-4/#ratios return value > 0.0f && value != +std::numeric_limits::infinity() && value != -std::numeric_limits::infinity(); } constexpr std::optional consumeColorToken( const CSSPreservedToken& token) { if (token.type() == CSSTokenType::Ident) { return parseCSSNamedColor(token.stringValue()); } else if (token.type() != CSSTokenType::Hash) { return {}; } // https://www.w3.org/TR/css-color-4/#hex-color std::string_view hexColorValue = token.stringValue(); if (isValidHexColor(hexColorValue)) { if (hexColorValue.length() == 3) { return CSSValue::color( hexToNumeric(hexColorValue.substr(0, 1), HexColorType::Short), hexToNumeric(hexColorValue.substr(1, 1), HexColorType::Short), hexToNumeric(hexColorValue.substr(2, 1), HexColorType::Short), 255u); } else if (hexColorValue.length() == 4) { return CSSValue::color( hexToNumeric(hexColorValue.substr(0, 1), HexColorType::Short), hexToNumeric(hexColorValue.substr(1, 1), HexColorType::Short), hexToNumeric(hexColorValue.substr(2, 1), HexColorType::Short), hexToNumeric(hexColorValue.substr(3, 1), HexColorType::Short)); } else if (hexColorValue.length() == 6) { return CSSValue::color( hexToNumeric(hexColorValue.substr(0, 2), HexColorType::Long), hexToNumeric(hexColorValue.substr(2, 2), HexColorType::Long), hexToNumeric(hexColorValue.substr(4, 2), HexColorType::Long), 255u); } else if (hexColorValue.length() == 8) { return CSSValue::color( hexToNumeric(hexColorValue.substr(0, 2), HexColorType::Long), hexToNumeric(hexColorValue.substr(2, 2), HexColorType::Long), hexToNumeric(hexColorValue.substr(4, 2), HexColorType::Long), hexToNumeric(hexColorValue.substr(6, 2), HexColorType::Long)); } } return {}; } CSSSyntaxParser parser_; }; template constexpr void parseCSSValue( std::string_view css, CSSValueVariant& value) { detail::CSSValueParser parser(css); parser.consumeWhitespace(); auto componentValue = parser.consumeValue(); parser.consumeWhitespace(); if (parser.isFinished()) { value = std::move(componentValue); } else { value = {}; } }; } // namespace detail /** * Parse a single CSS value. Returns a default-constructed * CSSValueVariant (CSSKeyword::Unset) on syntax error. */ template CSSValueVariant parseCSSValue(std::string_view css) { CSSValueVariant value; detail::parseCSSValue(css, value); return value; }; /** * Parses a CSS property into its declared value type. */ template constexpr CSSDeclaredValue parseCSSProp(std::string_view css) { // For now we only allow parsing props composed of a single component value. CSSDeclaredValue value; detail::parseCSSValue(css, value); return value; } } // namespace facebook::react