2023-08-17 15:18:41 +01:00
/*
2024-10-04 13:19:50 +02:00
* Copyright ( c ) 2018 - 2022 , Andreas Kling < andreas @ ladybird . org >
2023-08-17 15:18:41 +01:00
* Copyright ( c ) 2020 - 2021 , the SerenityOS developers .
2025-07-23 10:15:39 +01:00
* Copyright ( c ) 2021 - 2025 , Sam Atkins < sam @ ladybird . org >
2023-08-17 15:18:41 +01:00
* Copyright ( c ) 2021 , Tobias Christiansen < tobyase @ serenityos . org >
* Copyright ( c ) 2022 , MacDue < macdue @ dueutil . tech >
*
* SPDX - License - Identifier : BSD - 2 - Clause
*/
2025-07-23 10:15:39 +01:00
# include <LibWeb/CSS/Parser/ErrorReporter.h>
2023-08-17 15:18:41 +01:00
# include <LibWeb/CSS/Parser/Parser.h>
2023-12-10 22:06:55 +13:00
# include <LibWeb/Infra/Strings.h>
2023-08-17 15:18:41 +01:00
namespace Web : : CSS : : Parser {
Optional < SelectorList > Parser : : parse_as_selector ( SelectorParsingMode parsing_mode )
{
auto selector_list = parse_a_selector_list ( m_token_stream , SelectorType : : Standalone , parsing_mode ) ;
if ( ! selector_list . is_error ( ) )
return selector_list . release_value ( ) ;
return { } ;
}
Optional < SelectorList > Parser : : parse_as_relative_selector ( SelectorParsingMode parsing_mode )
{
auto selector_list = parse_a_selector_list ( m_token_stream , SelectorType : : Relative , parsing_mode ) ;
if ( ! selector_list . is_error ( ) )
return selector_list . release_value ( ) ;
return { } ;
}
2025-03-20 16:56:46 +00:00
Optional < Selector : : PseudoElementSelector > Parser : : parse_as_pseudo_element_selector ( )
2024-08-06 12:44:43 +01:00
{
// FIXME: This is quite janky. Selector parsing is not at all designed to allow parsing just a single part of a selector.
// So, this code parses a whole selector, then rejects it if it's not a single pseudo-element simple selector.
// Come back and fix this, future Sam!
auto maybe_selector_list = parse_a_selector_list ( m_token_stream , SelectorType : : Standalone , SelectorParsingMode : : Standard ) ;
if ( maybe_selector_list . is_error ( ) )
return { } ;
auto & selector_list = maybe_selector_list . value ( ) ;
if ( selector_list . size ( ) ! = 1 )
return { } ;
auto & selector = selector_list . first ( ) ;
if ( selector - > compound_selectors ( ) . size ( ) ! = 1 )
return { } ;
auto & first_compound_selector = selector - > compound_selectors ( ) . first ( ) ;
if ( first_compound_selector . simple_selectors . size ( ) ! = 1 )
return { } ;
auto & simple_selector = first_compound_selector . simple_selectors . first ( ) ;
if ( simple_selector . type ! = Selector : : SimpleSelector : : Type : : PseudoElement )
return { } ;
return simple_selector . pseudo_element ( ) ;
}
2024-11-13 15:49:43 +00:00
static NonnullRefPtr < Selector > create_invalid_selector ( Selector : : Combinator combinator , Vector < ComponentValue > component_values )
{
// Trim leading and trailing whitespace
while ( ! component_values . is_empty ( ) & & component_values . first ( ) . is ( Token : : Type : : Whitespace ) ) {
component_values . take_first ( ) ;
}
while ( ! component_values . is_empty ( ) & & component_values . last ( ) . is ( Token : : Type : : Whitespace ) ) {
component_values . take_last ( ) ;
}
Selector : : SimpleSelector simple {
. type = Selector : : SimpleSelector : : Type : : Invalid ,
. value = Selector : : SimpleSelector : : Invalid {
. component_values = move ( component_values ) ,
}
} ;
Selector : : CompoundSelector compound {
. combinator = combinator ,
. simple_selectors = { move ( simple ) }
} ;
return Selector : : create ( { move ( compound ) } ) ;
}
2023-08-17 15:18:41 +01:00
template < typename T >
Parser : : ParseErrorOr < SelectorList > Parser : : parse_a_selector_list ( TokenStream < T > & tokens , SelectorType mode , SelectorParsingMode parsing_mode )
{
2024-10-17 12:01:13 +01:00
SelectorList selectors ;
2024-11-22 19:48:50 +11:00
for ( ; ; ) {
auto selector_parts = consume_a_list_of_component_values ( tokens , Token : : Type : : Comma ) ;
2023-08-17 15:18:41 +01:00
auto stream = TokenStream ( selector_parts ) ;
auto selector = parse_complex_selector ( stream , mode ) ;
if ( selector . is_error ( ) ) {
2024-11-13 15:49:43 +00:00
if ( parsing_mode = = SelectorParsingMode : : Forgiving ) {
// Keep the invalid selector around for serialization and nesting
auto combinator = mode = = SelectorType : : Standalone ? Selector : : Combinator : : None : Selector : : Combinator : : Descendant ;
selectors . append ( create_invalid_selector ( combinator , move ( selector_parts ) ) ) ;
2024-11-22 19:48:50 +11:00
} else {
return selector . error ( ) ;
2024-11-13 15:49:43 +00:00
}
2024-11-22 19:48:50 +11:00
} else {
selectors . append ( selector . release_value ( ) ) ;
2023-08-17 15:18:41 +01:00
}
2024-11-22 19:48:50 +11:00
if ( tokens . is_empty ( ) )
break ;
tokens . discard_a_token ( ) ;
2023-08-17 15:18:41 +01:00
}
if ( selectors . is_empty ( ) & & parsing_mode ! = SelectorParsingMode : : Forgiving )
return ParseError : : SyntaxError ;
return selectors ;
}
template Parser : : ParseErrorOr < SelectorList > Parser : : parse_a_selector_list ( TokenStream < ComponentValue > & , SelectorType , SelectorParsingMode ) ;
template Parser : : ParseErrorOr < SelectorList > Parser : : parse_a_selector_list ( TokenStream < Token > & , SelectorType , SelectorParsingMode ) ;
Parser : : ParseErrorOr < NonnullRefPtr < Selector > > Parser : : parse_complex_selector ( TokenStream < ComponentValue > & tokens , SelectorType mode )
{
Vector < Selector : : CompoundSelector > compound_selectors ;
auto first_selector = TRY ( parse_compound_selector ( tokens ) ) ;
if ( ! first_selector . has_value ( ) )
return ParseError : : SyntaxError ;
if ( mode = = SelectorType : : Standalone ) {
if ( first_selector - > combinator ! = Selector : : Combinator : : Descendant )
return ParseError : : SyntaxError ;
first_selector - > combinator = Selector : : Combinator : : None ;
}
compound_selectors . append ( first_selector . release_value ( ) ) ;
while ( tokens . has_next_token ( ) ) {
auto compound_selector = TRY ( parse_compound_selector ( tokens ) ) ;
if ( ! compound_selector . has_value ( ) )
break ;
compound_selectors . append ( compound_selector . release_value ( ) ) ;
}
if ( compound_selectors . is_empty ( ) )
return ParseError : : SyntaxError ;
return Selector : : create ( move ( compound_selectors ) ) ;
}
Parser : : ParseErrorOr < Optional < Selector : : CompoundSelector > > Parser : : parse_compound_selector ( TokenStream < ComponentValue > & tokens )
{
2024-10-09 12:29:29 +01:00
tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
auto combinator = parse_selector_combinator ( tokens ) . value_or ( Selector : : Combinator : : Descendant ) ;
2024-10-09 12:29:29 +01:00
tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
Vector < Selector : : SimpleSelector > simple_selectors ;
while ( tokens . has_next_token ( ) ) {
auto component = TRY ( parse_simple_selector ( tokens ) ) ;
if ( ! component . has_value ( ) )
break ;
2024-11-08 20:14:46 +00:00
if ( component - > type = = Selector : : SimpleSelector : : Type : : TagName & & ! simple_selectors . is_empty ( ) ) {
// Tag-name selectors can only go at the beginning of a compound selector.
return ParseError : : SyntaxError ;
}
2023-08-17 15:18:41 +01:00
simple_selectors . append ( component . release_value ( ) ) ;
}
2024-11-21 16:58:18 +11:00
if ( simple_selectors . is_empty ( ) ) {
if ( tokens . has_next_token ( ) | | combinator ! = Selector : : Combinator : : Descendant )
return ParseError : : SyntaxError ;
2023-08-17 15:18:41 +01:00
return Optional < Selector : : CompoundSelector > { } ;
2024-11-21 16:58:18 +11:00
}
2023-08-17 15:18:41 +01:00
return Selector : : CompoundSelector { combinator , move ( simple_selectors ) } ;
}
Optional < Selector : : Combinator > Parser : : parse_selector_combinator ( TokenStream < ComponentValue > & tokens )
{
2024-10-09 12:29:29 +01:00
auto const & current_value = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( current_value . is ( Token : : Type : : Delim ) ) {
switch ( current_value . token ( ) . delim ( ) ) {
case ' > ' :
return Selector : : Combinator : : ImmediateChild ;
case ' + ' :
return Selector : : Combinator : : NextSibling ;
case ' ~ ' :
return Selector : : Combinator : : SubsequentSibling ;
case ' | ' : {
2024-10-09 12:29:29 +01:00
auto const & next = tokens . next_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( next . is ( Token : : Type : : EndOfFile ) )
return { } ;
if ( next . is_delim ( ' | ' ) ) {
2024-10-09 12:29:29 +01:00
tokens . discard_a_token ( ) ;
2023-08-17 15:18:41 +01:00
return Selector : : Combinator : : Column ;
}
}
}
}
tokens . reconsume_current_input_token ( ) ;
return { } ;
}
Optional < Selector : : SimpleSelector : : QualifiedName > Parser : : parse_selector_qualified_name ( TokenStream < ComponentValue > & tokens , AllowWildcardName allow_wildcard_name )
{
auto is_name = [ ] ( ComponentValue const & token ) {
return token . is_delim ( ' * ' ) | | token . is ( Token : : Type : : Ident ) ;
} ;
auto get_name = [ ] ( ComponentValue const & token ) {
if ( token . is_delim ( ' * ' ) )
2023-12-10 16:18:26 +13:00
return " * " _fly_string ;
return token . token ( ) . ident ( ) ;
2023-08-17 15:18:41 +01:00
} ;
// There are 3 possibilities here:
// (Where <name> and <namespace> are either an <ident> or a `*` delim)
// 1) `|<name>`
// 2) `<namespace>|<name>`
// 3) `<name>`
// Whitespace is forbidden between any of these parts. https://www.w3.org/TR/selectors-4/#white-space
auto transaction = tokens . begin_transaction ( ) ;
2024-10-31 21:43:21 +01:00
auto const & first_token = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( first_token . is_delim ( ' | ' ) ) {
// Case 1: `|<name>`
2024-10-09 12:29:29 +01:00
if ( is_name ( tokens . next_token ( ) ) ) {
2024-10-31 21:43:21 +01:00
auto const & name_token = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( allow_wildcard_name = = AllowWildcardName : : No & & name_token . is_delim ( ' * ' ) )
return { } ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : QualifiedName {
. namespace_type = Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : None ,
2023-12-10 16:18:26 +13:00
. name = get_name ( name_token ) ,
2023-08-17 15:18:41 +01:00
} ;
}
return { } ;
}
if ( ! is_name ( first_token ) )
return { } ;
2024-10-09 12:29:29 +01:00
if ( tokens . next_token ( ) . is_delim ( ' | ' ) & & is_name ( tokens . peek_token ( 1 ) ) ) {
2023-08-17 15:18:41 +01:00
// Case 2: `<namespace>|<name>`
2024-10-09 12:29:29 +01:00
tokens . discard_a_token ( ) ; // `|`
2023-12-10 16:18:26 +13:00
auto namespace_ = get_name ( first_token ) ;
2024-10-09 12:29:29 +01:00
auto name = get_name ( tokens . consume_a_token ( ) ) ;
2023-08-17 15:18:41 +01:00
if ( allow_wildcard_name = = AllowWildcardName : : No & & name = = " * " sv )
return { } ;
auto namespace_type = namespace_ = = " * " sv
? Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Any
: Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Named ;
2025-06-23 22:40:37 +12:00
// https://www.w3.org/TR/selectors-4/#invalid
// a simple selector containing an undeclared namespace prefix is invalid
if ( namespace_type = = Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Named & & ! m_declared_namespaces . contains ( namespace_ ) )
return { } ;
2025-06-09 22:33:36 +12:00
2023-08-17 15:18:41 +01:00
transaction . commit ( ) ;
return Selector : : SimpleSelector : : QualifiedName {
. namespace_type = namespace_type ,
. namespace_ = namespace_ ,
. name = name ,
} ;
}
// Case 3: `<name>`
auto & name_token = first_token ;
if ( allow_wildcard_name = = AllowWildcardName : : No & & name_token . is_delim ( ' * ' ) )
return { } ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : QualifiedName {
. namespace_type = Selector : : SimpleSelector : : QualifiedName : : NamespaceType : : Default ,
2023-12-10 16:18:26 +13:00
. name = get_name ( name_token ) ,
2023-08-17 15:18:41 +01:00
} ;
}
Parser : : ParseErrorOr < Selector : : SimpleSelector > Parser : : parse_attribute_simple_selector ( ComponentValue const & first_value )
{
2024-10-11 11:17:10 +01:00
auto attribute_tokens = TokenStream { first_value . block ( ) . value } ;
2023-08-17 15:18:41 +01:00
2024-10-09 12:29:29 +01:00
attribute_tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! attribute_tokens . has_next_token ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = first_value . to_string ( ) ,
. description = " Attribute selector is empty. " _string ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
auto maybe_qualified_name = parse_selector_qualified_name ( attribute_tokens , AllowWildcardName : : No ) ;
if ( ! maybe_qualified_name . has_value ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = first_value . to_string ( ) ,
. description = MUST ( String : : formatted ( " Expected qualified-name, got: '{}'. " , attribute_tokens . next_token ( ) . to_debug_string ( ) ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
2024-06-22 13:33:48 +02:00
auto qualified_name = maybe_qualified_name . release_value ( ) ;
2023-08-17 15:18:41 +01:00
Selector : : SimpleSelector simple_selector {
. type = Selector : : SimpleSelector : : Type : : Attribute ,
. value = Selector : : SimpleSelector : : Attribute {
. match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : HasAttribute ,
2024-06-22 13:33:48 +02:00
. qualified_name = qualified_name ,
2024-08-18 21:17:08 +01:00
. case_type = Selector : : SimpleSelector : : Attribute : : CaseType : : DefaultMatch ,
2023-08-17 15:18:41 +01:00
}
} ;
2024-10-09 12:29:29 +01:00
attribute_tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! attribute_tokens . has_next_token ( ) )
return simple_selector ;
2024-10-09 12:29:29 +01:00
auto const & delim_part = attribute_tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! delim_part . is ( Token : : Type : : Delim ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = first_value . to_string ( ) ,
. description = MUST ( String : : formatted ( " Expected delim for attribute comparison, got: '{}'. " , delim_part . to_debug_string ( ) ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
if ( delim_part . token ( ) . delim ( ) = = ' = ' ) {
simple_selector . attribute ( ) . match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : ExactValueMatch ;
} else {
if ( ! attribute_tokens . has_next_token ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = first_value . to_string ( ) ,
. description = " Attribute selector ended part way through a match type. " _string ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
2024-10-09 12:29:29 +01:00
auto const & delim_second_part = attribute_tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! delim_second_part . is_delim ( ' = ' ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = first_value . to_string ( ) ,
. description = MUST ( String : : formatted ( " Expected a double delim for attribute comparison, got: '{}{}'. " , delim_part . to_debug_string ( ) , delim_second_part . to_debug_string ( ) ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
switch ( delim_part . token ( ) . delim ( ) ) {
case ' ~ ' :
simple_selector . attribute ( ) . match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : ContainsWord ;
break ;
case ' * ' :
simple_selector . attribute ( ) . match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : ContainsString ;
break ;
case ' | ' :
simple_selector . attribute ( ) . match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : StartsWithSegment ;
break ;
case ' ^ ' :
simple_selector . attribute ( ) . match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : StartsWithString ;
break ;
case ' $ ' :
simple_selector . attribute ( ) . match_type = Selector : : SimpleSelector : : Attribute : : MatchType : : EndsWithString ;
break ;
default :
attribute_tokens . reconsume_current_input_token ( ) ;
}
}
2024-10-09 12:29:29 +01:00
attribute_tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! attribute_tokens . has_next_token ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = first_value . to_string ( ) ,
. description = " Attribute selector ended without a value to match. " _string ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
2024-10-09 12:29:29 +01:00
auto const & value_part = attribute_tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! value_part . is ( Token : : Type : : Ident ) & & ! value_part . is ( Token : : Type : : String ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = first_value . to_string ( ) ,
. description = MUST ( String : : formatted ( " Expected a string or ident for the value to match attribute against, got: '{}'. " , value_part . to_debug_string ( ) ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
2023-12-09 09:24:41 +13:00
auto const & value_string = value_part . token ( ) . is ( Token : : Type : : Ident ) ? value_part . token ( ) . ident ( ) : value_part . token ( ) . string ( ) ;
simple_selector . attribute ( ) . value = value_string . to_string ( ) ;
2023-08-17 15:18:41 +01:00
2024-10-09 12:29:29 +01:00
attribute_tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
// Handle case-sensitivity suffixes. https://www.w3.org/TR/selectors-4/#attribute-case
if ( attribute_tokens . has_next_token ( ) ) {
2024-10-09 12:29:29 +01:00
auto const & case_sensitivity_part = attribute_tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( case_sensitivity_part . is ( Token : : Type : : Ident ) ) {
auto case_sensitivity = case_sensitivity_part . token ( ) . ident ( ) ;
if ( case_sensitivity . equals_ignoring_ascii_case ( " i " sv ) ) {
simple_selector . attribute ( ) . case_type = Selector : : SimpleSelector : : Attribute : : CaseType : : CaseInsensitiveMatch ;
} else if ( case_sensitivity . equals_ignoring_ascii_case ( " s " sv ) ) {
simple_selector . attribute ( ) . case_type = Selector : : SimpleSelector : : Attribute : : CaseType : : CaseSensitiveMatch ;
} else {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = first_value . to_string ( ) ,
. description = MUST ( String : : formatted ( " Expected a \" i \" or \" s \" attribute selector case sensitivity identifier, got: '{}'. " , case_sensitivity_part . to_debug_string ( ) ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
} else {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = first_value . to_string ( ) ,
. description = MUST ( String : : formatted ( " Expected an attribute selector case sensitivity identifier, got: '{}' " , case_sensitivity_part . to_debug_string ( ) ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
}
2025-05-15 17:24:30 +01:00
attribute_tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( attribute_tokens . has_next_token ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = first_value . to_string ( ) ,
. description = " Trailing tokens in attribute selector. " _string ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
return simple_selector ;
}
Parser : : ParseErrorOr < Selector : : SimpleSelector > Parser : : parse_pseudo_simple_selector ( TokenStream < ComponentValue > & tokens )
{
auto peek_token_ends_selector = [ & ] ( ) - > bool {
2024-10-09 12:29:29 +01:00
auto const & value = tokens . next_token ( ) ;
2023-08-17 15:18:41 +01:00
return ( value . is ( Token : : Type : : EndOfFile ) | | value . is ( Token : : Type : : Whitespace ) | | value . is ( Token : : Type : : Comma ) ) ;
} ;
if ( peek_token_ends_selector ( ) )
return ParseError : : SyntaxError ;
2025-03-24 13:56:24 +00:00
// Note that we already consumed one colon before we entered this function.
// FIXME: Don't do that.
bool is_pseudo_element = false ;
2024-10-09 12:29:29 +01:00
if ( tokens . next_token ( ) . is ( Token : : Type : : Colon ) ) {
2025-03-24 13:56:24 +00:00
is_pseudo_element = true ;
2024-10-09 12:29:29 +01:00
tokens . discard_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( peek_token_ends_selector ( ) )
return ParseError : : SyntaxError ;
}
2025-03-24 13:56:24 +00:00
if ( is_pseudo_element ) {
2024-10-09 12:29:29 +01:00
auto const & name_token = tokens . consume_a_token ( ) ;
2025-03-24 13:56:24 +00:00
bool is_function = false ;
FlyString pseudo_name ;
if ( name_token . is ( Token : : Type : : Ident ) ) {
pseudo_name = name_token . token ( ) . ident ( ) ;
} else if ( name_token . is_function ( ) ) {
pseudo_name = name_token . function ( ) . name ;
is_function = true ;
} else {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = name_token . to_string ( ) ,
. description = MUST ( String : : formatted ( " Pseudo-element should be an ident or function, got: '{}' " , name_token . to_debug_string ( ) ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
2025-03-24 13:56:24 +00:00
bool is_aliased_pseudo = false ;
auto pseudo_element = pseudo_element_from_string ( pseudo_name ) ;
if ( ! pseudo_element . has_value ( ) ) {
pseudo_element = aliased_pseudo_element_from_string ( pseudo_name ) ;
is_aliased_pseudo = pseudo_element . has_value ( ) ;
}
if ( pseudo_element . has_value ( ) ) {
auto metadata = pseudo_element_metadata ( * pseudo_element ) ;
2023-08-17 15:18:41 +01:00
2024-11-14 12:18:10 +00:00
// :has() is fussy about pseudo-elements inside it
2025-03-19 14:58:22 +00:00
if ( m_pseudo_class_context . contains_slow ( PseudoClass : : Has ) & & ! is_has_allowed_pseudo_element ( * pseudo_element ) ) {
2024-11-14 12:18:10 +00:00
return ParseError : : SyntaxError ;
}
2025-03-24 13:56:24 +00:00
Selector : : PseudoElementSelector : : Value value = Empty { } ;
if ( is_function ) {
if ( ! metadata . is_valid_as_function ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " ::{} " , pseudo_name ) ) ,
. value_string = name_token . to_string ( ) ,
. description = " Not valid as a function. " _string ,
} ) ;
2025-03-24 13:56:24 +00:00
return ParseError : : SyntaxError ;
}
// Parse arguments
TokenStream function_tokens { name_token . function ( ) . value } ;
function_tokens . discard_whitespace ( ) ;
switch ( metadata . parameter_type ) {
case PseudoElementMetadata : : ParameterType : : None :
if ( function_tokens . has_next_token ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " ::{} " , pseudo_name ) ) ,
. value_string = name_token . to_string ( ) ,
. description = " Should have no arguments. " _string ,
} ) ;
2025-03-24 13:56:24 +00:00
return ParseError : : SyntaxError ;
}
break ;
2025-07-12 16:15:06 +12:00
case PseudoElementMetadata : : ParameterType : : CompoundSelector : {
auto compound_selector_or_error = parse_compound_selector ( function_tokens ) ;
if ( compound_selector_or_error . is_error ( ) | | ! compound_selector_or_error . value ( ) . has_value ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " ::{} " , pseudo_name ) ) ,
. value_string = name_token . to_string ( ) ,
. description = " Failed to parse argument as a compound selector. " _string ,
} ) ;
2025-07-12 16:15:06 +12:00
return ParseError : : SyntaxError ;
}
2025-09-04 13:16:23 +02:00
function_tokens . discard_whitespace ( ) ;
if ( function_tokens . has_next_token ( ) ) {
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " ::{} " , pseudo_name ) ) ,
. value_string = name_token . to_string ( ) ,
. description = " Trailing tokens after compound selector argument. " _string ,
} ) ;
return ParseError : : SyntaxError ;
}
2025-07-12 16:15:06 +12:00
auto compound_selector = compound_selector_or_error . release_value ( ) . release_value ( ) ;
compound_selector . combinator = Selector : : Combinator : : None ;
Vector compound_selectors { move ( compound_selector ) } ;
value = Selector : : create ( move ( compound_selectors ) ) ;
break ;
}
2025-03-24 13:56:24 +00:00
case PseudoElementMetadata : : ParameterType : : PTNameSelector : {
// <pt-name-selector> = '*' | <custom-ident>
// https://drafts.csswg.org/css-view-transitions-1/#typedef-pt-name-selector
if ( function_tokens . next_token ( ) . is_delim ( ' * ' ) ) {
function_tokens . discard_a_token ( ) ; // *
value = Selector : : PseudoElementSelector : : PTNameSelector { . is_universal = true } ;
} else if ( auto custom_ident = parse_custom_ident ( function_tokens , { } ) ; custom_ident . has_value ( ) ) {
value = Selector : : PseudoElementSelector : : PTNameSelector { . value = custom_ident . release_value ( ) } ;
} else {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " ::{} " , pseudo_name ) ) ,
. value_string = name_token . to_string ( ) ,
. description = MUST ( String : : formatted ( " Invalid <pt-name-selector> - expected `*` or `<custom-ident>`, got `{}` " , function_tokens . next_token ( ) . to_debug_string ( ) ) ) ,
} ) ;
2025-03-24 13:56:24 +00:00
return ParseError : : SyntaxError ;
}
function_tokens . discard_whitespace ( ) ;
if ( function_tokens . has_next_token ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " ::{} " , pseudo_name ) ) ,
. value_string = name_token . to_string ( ) ,
. description = " Invalid <pt-name-selector> - trailing tokens. " _string ,
} ) ;
2025-03-24 13:56:24 +00:00
return ParseError : : SyntaxError ;
}
break ;
}
}
2023-08-17 15:18:41 +01:00
2025-03-24 13:56:24 +00:00
} else {
if ( ! metadata . is_valid_as_identifier ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " ::{} " , pseudo_name ) ) ,
. value_string = name_token . to_string ( ) ,
. description = " Only valid as a function. " _string ,
} ) ;
2025-03-24 13:56:24 +00:00
return ParseError : : SyntaxError ;
}
}
// Aliased pseudo-elements behave like their target pseudo-element, but serialize as themselves. So store their
// name like we do for unknown -webkit pseudos below.
if ( is_aliased_pseudo ) {
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoElement ,
. value = Selector : : PseudoElementSelector { pseudo_element . release_value ( ) , pseudo_name . to_string ( ) . to_ascii_lowercase ( ) , move ( value ) }
} ;
2025-03-19 16:36:30 +00:00
}
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoElement ,
2025-03-24 13:56:24 +00:00
. value = Selector : : PseudoElementSelector { pseudo_element . release_value ( ) , move ( value ) }
2025-03-19 16:36:30 +00:00
} ;
}
2023-12-10 22:06:55 +13:00
// https://www.w3.org/TR/selectors-4/#compat
// All other pseudo-elements whose names begin with the string “-webkit-” (matched ASCII case-insensitively)
// and that are not functional notations must be treated as valid at parse time. (That is, ::-webkit-asdf is
// valid at parse time, but ::-webkit-jkl() is not.) If they’ re not otherwise recognized and supported, they
// must be treated as matching nothing, and are unknown -webkit- pseudo-elements.
2025-03-24 13:56:24 +00:00
if ( ! is_function & & pseudo_name . starts_with_bytes ( " -webkit- " sv , CaseSensitivity : : CaseInsensitive ) ) {
2024-11-14 12:18:10 +00:00
// :has() only allows a limited set of pseudo-elements inside it, which doesn't include unknown ones.
if ( m_pseudo_class_context . contains_slow ( PseudoClass : : Has ) )
return ParseError : : SyntaxError ;
2023-12-10 22:06:55 +13:00
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoElement ,
// Unknown -webkit- pseudo-elements must be serialized in ASCII lowercase.
2025-03-20 16:56:46 +00:00
. value = Selector : : PseudoElementSelector { PseudoElement : : UnknownWebKit , pseudo_name . to_string ( ) . to_ascii_lowercase ( ) } ,
2023-12-10 22:06:55 +13:00
} ;
}
2023-12-10 17:07:32 +13:00
if ( has_ignored_vendor_prefix ( pseudo_name ) )
return ParseError : : IncludesIgnoredVendorPrefix ;
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( UnknownPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " ::{} " , pseudo_name ) ) ,
} ) ;
2023-12-10 17:07:32 +13:00
return ParseError : : SyntaxError ;
2023-08-17 15:18:41 +01:00
}
if ( peek_token_ends_selector ( ) )
return ParseError : : SyntaxError ;
2024-10-09 12:29:29 +01:00
auto const & pseudo_class_token = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( pseudo_class_token . is ( Token : : Type : : Ident ) ) {
auto pseudo_name = pseudo_class_token . token ( ) . ident ( ) ;
if ( has_ignored_vendor_prefix ( pseudo_name ) )
return ParseError : : IncludesIgnoredVendorPrefix ;
auto make_pseudo_class_selector = [ ] ( auto pseudo_class ) {
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector { . type = pseudo_class }
} ;
} ;
if ( auto pseudo_class = pseudo_class_from_string ( pseudo_name ) ; pseudo_class . has_value ( ) ) {
if ( ! pseudo_class_metadata ( pseudo_class . value ( ) ) . is_valid_as_identifier ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_name ) ) ,
. value_string = pseudo_class_token . to_string ( ) ,
. description = " Only valid as a function. " _string ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
return make_pseudo_class_selector ( pseudo_class . value ( ) ) ;
}
// Single-colon syntax allowed for ::after, ::before, ::first-letter and ::first-line for compatibility.
// https://www.w3.org/TR/selectors/#pseudo-element-syntax
2025-03-19 14:58:22 +00:00
if ( auto pseudo_element = pseudo_element_from_string ( pseudo_name ) ; pseudo_element . has_value ( ) ) {
switch ( pseudo_element . value ( ) ) {
2025-03-20 16:56:46 +00:00
case PseudoElement : : After :
case PseudoElement : : Before :
case PseudoElement : : FirstLetter :
case PseudoElement : : FirstLine :
2024-11-14 12:18:10 +00:00
// :has() is fussy about pseudo-elements inside it
2025-03-19 14:58:22 +00:00
if ( m_pseudo_class_context . contains_slow ( PseudoClass : : Has ) & & ! is_has_allowed_pseudo_element ( pseudo_element . value ( ) ) ) {
2024-11-14 12:18:10 +00:00
return ParseError : : SyntaxError ;
}
2023-08-17 15:18:41 +01:00
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoElement ,
2025-03-19 14:58:22 +00:00
. value = Selector : : PseudoElementSelector { pseudo_element . value ( ) }
2023-08-17 15:18:41 +01:00
} ;
default :
break ;
}
}
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( UnknownPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_name ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
if ( pseudo_class_token . is_function ( ) ) {
2025-08-28 10:44:17 +01:00
auto parse_an_plus_b_selector = [ this ] ( auto pseudo_class , Vector < ComponentValue > const & function_values , bool allow_of = false ) - > ParseErrorOr < Selector : : SimpleSelector > {
2023-08-17 15:18:41 +01:00
auto tokens = TokenStream < ComponentValue > ( function_values ) ;
2025-08-12 10:28:05 +01:00
auto an_plus_b_pattern = parse_a_n_plus_b_pattern ( tokens ) ;
if ( ! an_plus_b_pattern . has_value ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_class_name ( pseudo_class ) ) ) ,
. value_string = tokens . dump_string ( ) ,
. description = " Invalid An+B format. " _string ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
2024-10-09 12:29:29 +01:00
tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! tokens . has_next_token ( ) ) {
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
2025-08-28 10:44:17 +01:00
. an_plus_b_pattern = an_plus_b_pattern . release_value ( ) }
2023-08-17 15:18:41 +01:00
} ;
}
if ( ! allow_of )
return ParseError : : SyntaxError ;
// Parse the `of <selector-list>` syntax
2024-10-09 12:29:29 +01:00
auto const & maybe_of = tokens . consume_a_token ( ) ;
2023-11-21 12:08:39 +00:00
if ( ! maybe_of . is_ident ( " of " sv ) )
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
2024-10-09 12:29:29 +01:00
tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
auto selector_list = TRY ( parse_a_selector_list ( tokens , SelectorType : : Standalone ) ) ;
2024-10-09 12:29:29 +01:00
tokens . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( tokens . has_next_token ( ) )
return ParseError : : SyntaxError ;
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
2025-08-28 10:44:17 +01:00
. an_plus_b_pattern = an_plus_b_pattern . release_value ( ) ,
2023-08-17 15:18:41 +01:00
. argument_selector_list = move ( selector_list ) }
} ;
} ;
auto const & pseudo_function = pseudo_class_token . function ( ) ;
2024-10-11 11:17:10 +01:00
auto maybe_pseudo_class = pseudo_class_from_string ( pseudo_function . name ) ;
2023-08-17 15:18:41 +01:00
if ( ! maybe_pseudo_class . has_value ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( UnknownPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_function . name ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
auto pseudo_class = maybe_pseudo_class . value ( ) ;
auto metadata = pseudo_class_metadata ( pseudo_class ) ;
if ( ! metadata . is_valid_as_function ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_function . name ) ) ,
. value_string = pseudo_class_token . to_string ( ) ,
. description = " Not valid as a function. " _string ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
2024-10-11 11:17:10 +01:00
if ( pseudo_function . value . is_empty ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_function . name ) ) ,
. value_string = pseudo_class_token . to_string ( ) ,
. description = " Missing arguments. " _string ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
2024-11-14 12:18:10 +00:00
// "The :has() pseudo-class cannot be nested; :has() is not valid within :has()."
// https://drafts.csswg.org/selectors/#relational
if ( pseudo_class = = PseudoClass : : Has & & m_pseudo_class_context . contains_slow ( PseudoClass : : Has ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_function . name ) ) ,
. value_string = pseudo_class_token . to_string ( ) ,
. description = " :has() is not allowed inside :has(). " _string ,
} ) ;
2024-11-14 12:18:10 +00:00
return ParseError : : SyntaxError ;
}
m_pseudo_class_context . append ( pseudo_class ) ;
ScopeGuard guard = [ & ] { m_pseudo_class_context . take_last ( ) ; } ;
2023-08-17 15:18:41 +01:00
switch ( metadata . parameter_type ) {
case PseudoClassMetadata : : ParameterType : : ANPlusB :
2025-08-12 10:28:05 +01:00
return parse_an_plus_b_selector ( pseudo_class , pseudo_function . value , false ) ;
2023-08-17 15:18:41 +01:00
case PseudoClassMetadata : : ParameterType : : ANPlusBOf :
2025-08-12 10:28:05 +01:00
return parse_an_plus_b_selector ( pseudo_class , pseudo_function . value , true ) ;
2023-08-17 15:18:41 +01:00
case PseudoClassMetadata : : ParameterType : : CompoundSelector : {
2024-10-11 11:17:10 +01:00
auto function_token_stream = TokenStream ( pseudo_function . value ) ;
2023-08-17 15:18:41 +01:00
auto compound_selector_or_error = parse_compound_selector ( function_token_stream ) ;
if ( compound_selector_or_error . is_error ( ) | | ! compound_selector_or_error . value ( ) . has_value ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_function . name ) ) ,
. value_string = pseudo_class_token . to_string ( ) ,
. description = " Failed to parse argument as a compound selector. " _string ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
2024-07-23 15:03:27 +02:00
auto compound_selector = compound_selector_or_error . release_value ( ) . release_value ( ) ;
compound_selector . combinator = Selector : : Combinator : : None ;
Vector compound_selectors { move ( compound_selector ) } ;
2023-08-17 15:18:41 +01:00
auto selector = Selector : : create ( move ( compound_selectors ) ) ;
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
. argument_selector_list = { move ( selector ) } }
} ;
}
2024-07-13 09:19:30 -07:00
case PseudoClassMetadata : : ParameterType : : ForgivingRelativeSelectorList :
2023-08-17 15:18:41 +01:00
case PseudoClassMetadata : : ParameterType : : ForgivingSelectorList : {
2024-10-11 11:17:10 +01:00
auto function_token_stream = TokenStream ( pseudo_function . value ) ;
2024-07-13 09:19:30 -07:00
auto selector_type = metadata . parameter_type = = PseudoClassMetadata : : ParameterType : : ForgivingSelectorList
? SelectorType : : Standalone
: SelectorType : : Relative ;
2023-08-17 15:18:41 +01:00
// NOTE: Because it's forgiving, even complete garbage will parse OK as an empty selector-list.
2024-07-13 09:19:30 -07:00
auto argument_selector_list = MUST ( parse_a_selector_list ( function_token_stream , selector_type , SelectorParsingMode : : Forgiving ) ) ;
2023-08-17 15:18:41 +01:00
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
2024-11-14 12:48:14 +00:00
. is_forgiving = true ,
2023-08-17 15:18:41 +01:00
. argument_selector_list = move ( argument_selector_list ) }
} ;
}
2023-08-12 18:11:50 +01:00
case PseudoClassMetadata : : ParameterType : : Ident : {
2024-10-11 11:17:10 +01:00
auto function_token_stream = TokenStream ( pseudo_function . value ) ;
2024-10-09 12:29:29 +01:00
function_token_stream . discard_whitespace ( ) ;
2025-05-16 14:57:18 +01:00
auto const & maybe_ident_token = function_token_stream . consume_a_token ( ) ;
2024-10-09 12:29:29 +01:00
function_token_stream . discard_whitespace ( ) ;
2025-05-16 14:57:18 +01:00
if ( ! maybe_ident_token . is ( Token : : Type : : Ident ) | | function_token_stream . has_next_token ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_function . name ) ) ,
. value_string = pseudo_class_token . to_string ( ) ,
. description = " Failed to parse argument as an ident. " _string ,
} ) ;
2023-08-12 18:11:50 +01:00
return ParseError : : SyntaxError ;
}
2025-05-16 14:57:18 +01:00
auto & ident = maybe_ident_token . token ( ) . ident ( ) ;
2023-08-12 18:11:50 +01:00
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
2025-05-16 14:57:18 +01:00
. ident = Selector : : SimpleSelector : : PseudoClassSelector : : Ident {
. keyword = keyword_from_string ( ident ) . value_or ( Keyword : : Invalid ) ,
. string_value = ident ,
} ,
}
2023-08-12 18:11:50 +01:00
} ;
}
2023-08-17 15:18:41 +01:00
case PseudoClassMetadata : : ParameterType : : LanguageRanges : {
Vector < FlyString > languages ;
2024-10-11 11:17:10 +01:00
auto function_token_stream = TokenStream ( pseudo_function . value ) ;
2023-08-17 15:18:41 +01:00
auto language_token_lists = parse_a_comma_separated_list_of_component_values ( function_token_stream ) ;
2024-10-31 21:43:21 +01:00
for ( auto const & language_token_list : language_token_lists ) {
2023-08-17 15:18:41 +01:00
auto language_token_stream = TokenStream ( language_token_list ) ;
2024-10-09 12:29:29 +01:00
language_token_stream . discard_whitespace ( ) ;
2024-10-31 21:43:21 +01:00
auto const & language_token = language_token_stream . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! ( language_token . is ( Token : : Type : : Ident ) | | language_token . is ( Token : : Type : : String ) ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_function . name ) ) ,
. value_string = pseudo_class_token . to_string ( ) ,
. description = " Failed to parse argument as a language range: Not a string/ident. " _string ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
auto language_string = language_token . is ( Token : : Type : : String ) ? language_token . token ( ) . string ( ) : language_token . token ( ) . ident ( ) ;
2023-12-10 16:18:26 +13:00
languages . append ( language_string ) ;
2023-08-17 15:18:41 +01:00
2024-10-09 12:29:29 +01:00
language_token_stream . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
if ( language_token_stream . has_next_token ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_function . name ) ) ,
. value_string = pseudo_class_token . to_string ( ) ,
. description = " Failed to parse argument as a language range: Has trailing tokens. " _string ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
}
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
. languages = move ( languages ) }
} ;
}
2025-08-28 10:29:57 +01:00
case PseudoClassMetadata : : ParameterType : : LevelList : {
// https://drafts.csswg.org/selectors-5/#heading-functional-pseudo
// :heading() = :heading( <level># )
// where <level> is a <number-token> with its type flag set to "integer".
Vector < i64 > levels ;
auto function_token_stream = TokenStream ( pseudo_function . value ) ;
auto level_lists = parse_a_comma_separated_list_of_component_values ( function_token_stream ) ;
for ( auto const & level_tokens : level_lists ) {
TokenStream level_token_stream { level_tokens } ;
level_token_stream . discard_whitespace ( ) ;
auto & maybe_integer = level_token_stream . consume_a_token ( ) ;
level_token_stream . discard_whitespace ( ) ;
if ( ! maybe_integer . is ( Token : : Type : : Number ) | | ! maybe_integer . token ( ) . number ( ) . is_integer ( ) ) {
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_function . name ) ) ,
. value_string = pseudo_class_token . to_string ( ) ,
. description = " Failed to parse argument as a <level>: Not an <integer> literal. " _string ,
} ) ;
return ParseError : : SyntaxError ;
}
if ( level_token_stream . has_next_token ( ) ) {
ErrorReporter : : the ( ) . report ( InvalidPseudoClassOrElementError {
. name = MUST ( String : : formatted ( " :{} " , pseudo_function . name ) ) ,
. value_string = pseudo_class_token . to_string ( ) ,
. description = " Failed to parse argument as a <level>: Has trailing tokens. " _string ,
} ) ;
return ParseError : : SyntaxError ;
}
levels . append ( maybe_integer . token ( ) . number ( ) . integer_value ( ) ) ;
}
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
. levels = move ( levels ) ,
}
} ;
}
2024-11-14 11:22:46 +00:00
case PseudoClassMetadata : : ParameterType : : RelativeSelectorList :
2023-08-17 15:18:41 +01:00
case PseudoClassMetadata : : ParameterType : : SelectorList : {
2024-10-11 11:17:10 +01:00
auto function_token_stream = TokenStream ( pseudo_function . value ) ;
2024-11-14 11:22:46 +00:00
auto selector_type = metadata . parameter_type = = PseudoClassMetadata : : ParameterType : : SelectorList
? SelectorType : : Standalone
: SelectorType : : Relative ;
auto not_selector = TRY ( parse_a_selector_list ( function_token_stream , selector_type ) ) ;
2023-08-17 15:18:41 +01:00
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : PseudoClass ,
. value = Selector : : SimpleSelector : : PseudoClassSelector {
. type = pseudo_class ,
. argument_selector_list = move ( not_selector ) }
} ;
}
case PseudoClassMetadata : : ParameterType : : None :
// `None` means this is not a function-type pseudo-class, so this state should be impossible.
VERIFY_NOT_REACHED ( ) ;
}
}
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = pseudo_class_token . to_string ( ) ,
. description = MUST ( String : : formatted ( " Pseudo-class should be an ident or function, got: '{}' " , pseudo_class_token . to_debug_string ( ) ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
Parser : : ParseErrorOr < Optional < Selector : : SimpleSelector > > Parser : : parse_simple_selector ( TokenStream < ComponentValue > & tokens )
{
auto peek_token_ends_selector = [ & ] ( ) - > bool {
2024-10-09 12:29:29 +01:00
auto const & value = tokens . next_token ( ) ;
2023-08-17 15:18:41 +01:00
return ( value . is ( Token : : Type : : EndOfFile ) | | value . is ( Token : : Type : : Whitespace ) | | value . is ( Token : : Type : : Comma ) ) ;
} ;
if ( peek_token_ends_selector ( ) )
return Optional < Selector : : SimpleSelector > { } ;
// Handle universal and tag-name types together, since both can be namespaced
if ( auto qualified_name = parse_selector_qualified_name ( tokens , AllowWildcardName : : Yes ) ; qualified_name . has_value ( ) ) {
if ( qualified_name - > name . name = = " * " sv ) {
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : Universal ,
. value = qualified_name . release_value ( ) ,
} ;
}
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : TagName ,
. value = qualified_name . release_value ( ) ,
} ;
}
2024-10-09 12:29:29 +01:00
auto const & first_value = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( first_value . is ( Token : : Type : : Delim ) ) {
u32 delim = first_value . token ( ) . delim ( ) ;
switch ( delim ) {
case ' * ' :
// Handled already
VERIFY_NOT_REACHED ( ) ;
2024-10-15 12:00:29 +01:00
case ' & ' :
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : Nesting ,
} ;
2023-08-17 15:18:41 +01:00
case ' . ' : {
if ( peek_token_ends_selector ( ) )
return ParseError : : SyntaxError ;
2024-10-09 12:29:29 +01:00
auto const & class_name_value = tokens . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( ! class_name_value . is ( Token : : Type : : Ident ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = tokens . dump_string ( ) ,
. description = MUST ( String : : formatted ( " Expected an ident after '.', got: {} " , class_name_value . to_debug_string ( ) ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : Class ,
2023-12-10 16:18:26 +13:00
. value = Selector : : SimpleSelector : : Name { class_name_value . token ( ) . ident ( ) }
2023-08-17 15:18:41 +01:00
} ;
}
case ' > ' :
case ' + ' :
case ' ~ ' :
case ' | ' :
// Whitespace is not required between the compound-selector and a combinator.
// So, if we see a combinator, return that this compound-selector is done, instead of a syntax error.
tokens . reconsume_current_input_token ( ) ;
return Optional < Selector : : SimpleSelector > { } ;
default :
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = tokens . dump_string ( ) ,
. description = MUST ( String : : formatted ( " Unrecognized delimiter: {} " , first_value . token ( ) . to_string ( ) ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
}
if ( first_value . is ( Token : : Type : : Hash ) ) {
if ( first_value . token ( ) . hash_type ( ) ! = Token : : HashType : : Id ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = tokens . dump_string ( ) ,
. description = MUST ( String : : formatted ( " Hash token is not an id: {} " , first_value . to_debug_string ( ) ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
return Selector : : SimpleSelector {
. type = Selector : : SimpleSelector : : Type : : Id ,
2023-12-10 16:18:26 +13:00
. value = Selector : : SimpleSelector : : Name { first_value . token ( ) . hash_value ( ) }
2023-08-17 15:18:41 +01:00
} ;
}
if ( first_value . is_block ( ) & & first_value . block ( ) . is_square ( ) )
return TRY ( parse_attribute_simple_selector ( first_value ) ) ;
if ( first_value . is ( Token : : Type : : Colon ) )
return TRY ( parse_pseudo_simple_selector ( tokens ) ) ;
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. value_string = tokens . dump_string ( ) ,
. description = MUST ( String : : formatted ( " Invalid start of a simple selector: {} " , first_value . to_debug_string ( ) ) ) ,
} ) ;
2023-08-17 15:18:41 +01:00
return ParseError : : SyntaxError ;
}
Optional < Selector : : SimpleSelector : : ANPlusBPattern > Parser : : parse_a_n_plus_b_pattern ( TokenStream < ComponentValue > & values )
{
auto transaction = values . begin_transaction ( ) ;
auto is_sign = [ ] ( ComponentValue const & value ) - > bool {
return value . is ( Token : : Type : : Delim ) & & ( value . token ( ) . delim ( ) = = ' + ' | | value . token ( ) . delim ( ) = = ' - ' ) ;
} ;
auto is_n_dimension = [ ] ( ComponentValue const & value ) - > bool {
if ( ! value . is ( Token : : Type : : Dimension ) )
return false ;
if ( ! value . token ( ) . number ( ) . is_integer ( ) )
return false ;
if ( ! value . token ( ) . dimension_unit ( ) . equals_ignoring_ascii_case ( " n " sv ) )
return false ;
return true ;
} ;
auto is_ndash_dimension = [ ] ( ComponentValue const & value ) - > bool {
if ( ! value . is ( Token : : Type : : Dimension ) )
return false ;
if ( ! value . token ( ) . number ( ) . is_integer ( ) )
return false ;
if ( ! value . token ( ) . dimension_unit ( ) . equals_ignoring_ascii_case ( " n- " sv ) )
return false ;
return true ;
} ;
auto is_ndashdigit_dimension = [ ] ( ComponentValue const & value ) - > bool {
if ( ! value . is ( Token : : Type : : Dimension ) )
return false ;
if ( ! value . token ( ) . number ( ) . is_integer ( ) )
return false ;
auto dimension_unit = value . token ( ) . dimension_unit ( ) ;
2023-11-26 10:40:08 +13:00
if ( ! dimension_unit . starts_with_bytes ( " n- " sv , CaseSensitivity : : CaseInsensitive ) )
2023-08-17 15:18:41 +01:00
return false ;
2023-11-26 10:40:08 +13:00
for ( size_t i = 2 ; i < dimension_unit . bytes_as_string_view ( ) . length ( ) ; + + i ) {
if ( ! is_ascii_digit ( dimension_unit . bytes_as_string_view ( ) [ i ] ) )
2023-08-17 15:18:41 +01:00
return false ;
}
return true ;
} ;
auto is_ndashdigit_ident = [ ] ( ComponentValue const & value ) - > bool {
if ( ! value . is ( Token : : Type : : Ident ) )
return false ;
auto ident = value . token ( ) . ident ( ) ;
2023-11-07 00:22:33 +13:00
if ( ! ident . starts_with_bytes ( " n- " sv , CaseSensitivity : : CaseInsensitive ) )
2023-08-17 15:18:41 +01:00
return false ;
2023-11-07 00:22:33 +13:00
for ( size_t i = 2 ; i < ident . bytes_as_string_view ( ) . length ( ) ; + + i ) {
if ( ! is_ascii_digit ( ident . bytes_as_string_view ( ) [ i ] ) )
2023-08-17 15:18:41 +01:00
return false ;
}
return true ;
} ;
auto is_dashndashdigit_ident = [ ] ( ComponentValue const & value ) - > bool {
if ( ! value . is ( Token : : Type : : Ident ) )
return false ;
auto ident = value . token ( ) . ident ( ) ;
2023-11-07 00:22:33 +13:00
if ( ! ident . starts_with_bytes ( " -n- " sv , CaseSensitivity : : CaseInsensitive ) )
2023-08-17 15:18:41 +01:00
return false ;
2023-11-07 00:22:33 +13:00
if ( ident . bytes_as_string_view ( ) . length ( ) = = 3 )
2023-08-17 15:18:41 +01:00
return false ;
2023-11-07 00:22:33 +13:00
for ( size_t i = 3 ; i < ident . bytes_as_string_view ( ) . length ( ) ; + + i ) {
if ( ! is_ascii_digit ( ident . bytes_as_string_view ( ) [ i ] ) )
2023-08-17 15:18:41 +01:00
return false ;
}
return true ;
} ;
auto is_integer = [ ] ( ComponentValue const & value ) - > bool {
return value . is ( Token : : Type : : Number ) & & value . token ( ) . number ( ) . is_integer ( ) ;
} ;
auto is_signed_integer = [ ] ( ComponentValue const & value ) - > bool {
return value . is ( Token : : Type : : Number ) & & value . token ( ) . number ( ) . is_integer_with_explicit_sign ( ) ;
} ;
auto is_signless_integer = [ ] ( ComponentValue const & value ) - > bool {
return value . is ( Token : : Type : : Number ) & & ! value . token ( ) . number ( ) . is_integer_with_explicit_sign ( ) ;
} ;
// https://www.w3.org/TR/css-syntax-3/#the-anb-type
// Unfortunately these can't be in the same order as in the spec.
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
auto const & first_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
// odd | even
if ( first_value . is ( Token : : Type : : Ident ) ) {
auto ident = first_value . token ( ) . ident ( ) ;
if ( ident . equals_ignoring_ascii_case ( " odd " sv ) ) {
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 2 , 1 } ;
}
if ( ident . equals_ignoring_ascii_case ( " even " sv ) ) {
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 2 , 0 } ;
}
}
// <integer>
if ( is_integer ( first_value ) ) {
int b = first_value . token ( ) . to_integer ( ) ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 0 , b } ;
}
// <n-dimension>
// <n-dimension> <signed-integer>
// <n-dimension> ['+' | '-'] <signless-integer>
if ( is_n_dimension ( first_value ) ) {
int a = first_value . token ( ) . dimension_value_int ( ) ;
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
// <n-dimension> <signed-integer>
2024-10-09 12:29:29 +01:00
if ( is_signed_integer ( values . next_token ( ) ) ) {
int b = values . consume_a_token ( ) . token ( ) . to_integer ( ) ;
2023-08-17 15:18:41 +01:00
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { a , b } ;
}
// <n-dimension> ['+' | '-'] <signless-integer>
{
auto child_transaction = transaction . create_child ( ) ;
2024-10-09 12:29:29 +01:00
auto const & second_value = values . consume_a_token ( ) ;
values . discard_whitespace ( ) ;
auto const & third_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( is_sign ( second_value ) & & is_signless_integer ( third_value ) ) {
int b = third_value . token ( ) . to_integer ( ) * ( second_value . is_delim ( ' + ' ) ? 1 : - 1 ) ;
child_transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { a , b } ;
}
}
// <n-dimension>
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { a , 0 } ;
}
// <ndash-dimension> <signless-integer>
if ( is_ndash_dimension ( first_value ) ) {
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
auto const & second_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( is_signless_integer ( second_value ) ) {
int a = first_value . token ( ) . dimension_value_int ( ) ;
int b = - second_value . token ( ) . to_integer ( ) ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { a , b } ;
}
2025-07-23 10:15:39 +01:00
return { } ;
2023-08-17 15:18:41 +01:00
}
// <ndashdigit-dimension>
if ( is_ndashdigit_dimension ( first_value ) ) {
auto const & dimension = first_value . token ( ) ;
int a = dimension . dimension_value_int ( ) ;
2023-12-23 15:59:14 +13:00
auto maybe_b = dimension . dimension_unit ( ) . bytes_as_string_view ( ) . substring_view ( 1 ) . to_number < int > ( ) ;
2023-08-17 15:18:41 +01:00
if ( maybe_b . has_value ( ) ) {
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { a , maybe_b . value ( ) } ;
}
2025-07-23 10:15:39 +01:00
return { } ;
2023-08-17 15:18:41 +01:00
}
// <dashndashdigit-ident>
if ( is_dashndashdigit_ident ( first_value ) ) {
2023-12-23 15:59:14 +13:00
auto maybe_b = first_value . token ( ) . ident ( ) . bytes_as_string_view ( ) . substring_view ( 2 ) . to_number < int > ( ) ;
2023-08-17 15:18:41 +01:00
if ( maybe_b . has_value ( ) ) {
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { - 1 , maybe_b . value ( ) } ;
}
2025-07-23 10:15:39 +01:00
return { } ;
2023-08-17 15:18:41 +01:00
}
// -n
// -n <signed-integer>
// -n ['+' | '-'] <signless-integer>
2023-11-21 12:08:39 +00:00
if ( first_value . is_ident ( " -n " sv ) ) {
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
// -n <signed-integer>
2024-10-09 12:29:29 +01:00
if ( is_signed_integer ( values . next_token ( ) ) ) {
int b = values . consume_a_token ( ) . token ( ) . to_integer ( ) ;
2023-08-17 15:18:41 +01:00
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { - 1 , b } ;
}
// -n ['+' | '-'] <signless-integer>
{
auto child_transaction = transaction . create_child ( ) ;
2024-10-09 12:29:29 +01:00
auto const & second_value = values . consume_a_token ( ) ;
values . discard_whitespace ( ) ;
auto const & third_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( is_sign ( second_value ) & & is_signless_integer ( third_value ) ) {
int b = third_value . token ( ) . to_integer ( ) * ( second_value . is_delim ( ' + ' ) ? 1 : - 1 ) ;
child_transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { - 1 , b } ;
}
}
// -n
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { - 1 , 0 } ;
}
// -n- <signless-integer>
2023-11-21 12:08:39 +00:00
if ( first_value . is_ident ( " -n- " sv ) ) {
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
auto const & second_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( is_signless_integer ( second_value ) ) {
int b = - second_value . token ( ) . to_integer ( ) ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { - 1 , b } ;
}
2025-07-23 10:15:39 +01:00
return { } ;
2023-08-17 15:18:41 +01:00
}
// All that's left now are these:
// '+'?† n
// '+'?† n <signed-integer>
// '+'?† n ['+' | '-'] <signless-integer>
// '+'?† n- <signless-integer>
// '+'?† <ndashdigit-ident>
// In all of these cases, the + is optional, and has no effect.
// So, we just skip the +, and carry on.
if ( ! first_value . is_delim ( ' + ' ) ) {
values . reconsume_current_input_token ( ) ;
// We do *not* skip whitespace here.
}
2024-10-09 12:29:29 +01:00
auto const & first_after_plus = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
// '+'?† n
// '+'?† n <signed-integer>
// '+'?† n ['+' | '-'] <signless-integer>
2023-11-21 12:08:39 +00:00
if ( first_after_plus . is_ident ( " n " sv ) ) {
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
2023-08-17 15:18:41 +01:00
// '+'?† n <signed-integer>
2024-10-09 12:29:29 +01:00
if ( is_signed_integer ( values . next_token ( ) ) ) {
int b = values . consume_a_token ( ) . token ( ) . to_integer ( ) ;
2023-08-17 15:18:41 +01:00
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 1 , b } ;
}
// '+'?† n ['+' | '-'] <signless-integer>
{
auto child_transaction = transaction . create_child ( ) ;
2024-10-09 12:29:29 +01:00
auto const & second_value = values . consume_a_token ( ) ;
values . discard_whitespace ( ) ;
auto const & third_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( is_sign ( second_value ) & & is_signless_integer ( third_value ) ) {
int b = third_value . token ( ) . to_integer ( ) * ( second_value . is_delim ( ' + ' ) ? 1 : - 1 ) ;
child_transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 1 , b } ;
}
}
// '+'?† n
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 1 , 0 } ;
}
// '+'?† n- <signless-integer>
2023-11-21 12:08:39 +00:00
if ( first_after_plus . is_ident ( " n- " sv ) ) {
2024-10-09 12:29:29 +01:00
values . discard_whitespace ( ) ;
auto const & second_value = values . consume_a_token ( ) ;
2023-08-17 15:18:41 +01:00
if ( is_signless_integer ( second_value ) ) {
int b = - second_value . token ( ) . to_integer ( ) ;
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 1 , b } ;
}
2025-07-23 10:15:39 +01:00
return { } ;
2023-08-17 15:18:41 +01:00
}
// '+'?† <ndashdigit-ident>
if ( is_ndashdigit_ident ( first_after_plus ) ) {
2023-12-23 15:59:14 +13:00
auto maybe_b = first_after_plus . token ( ) . ident ( ) . bytes_as_string_view ( ) . substring_view ( 1 ) . to_number < int > ( ) ;
2023-08-17 15:18:41 +01:00
if ( maybe_b . has_value ( ) ) {
transaction . commit ( ) ;
return Selector : : SimpleSelector : : ANPlusBPattern { 1 , maybe_b . value ( ) } ;
}
2025-07-23 10:15:39 +01:00
return { } ;
2023-08-17 15:18:41 +01:00
}
2025-07-23 10:15:39 +01:00
return { } ;
2023-08-17 15:18:41 +01:00
}
2025-05-16 11:37:53 +01:00
Optional < PageSelectorList > Parser : : parse_as_page_selector_list ( )
{
auto selector_list = parse_a_page_selector_list ( m_token_stream ) ;
if ( ! selector_list . is_error ( ) )
return selector_list . release_value ( ) ;
return { } ;
}
template < typename T >
Parser : : ParseErrorOr < PageSelectorList > Parser : : parse_a_page_selector_list ( TokenStream < T > & tokens )
{
// https://drafts.csswg.org/css-page-3/#syntax-page-selector
// <page-selector-list> = <page-selector>#
// <page-selector> = [ <ident-token>? <pseudo-page>* ]!
// <pseudo-page> = : [ left | right | first | blank ]
PageSelectorList selector_list ;
tokens . discard_whitespace ( ) ;
while ( tokens . has_next_token ( ) ) {
// First optional ident
Optional < FlyString > maybe_ident ;
if ( tokens . next_token ( ) . is ( Token : : Type : : Ident ) )
maybe_ident = static_cast < Token > ( tokens . consume_a_token ( ) ) . ident ( ) ;
// Then an optional series of pseudo-classes
Vector < PagePseudoClass > pseudo_classes ;
while ( tokens . next_token ( ) . is ( Token : : Type : : Colon ) ) {
tokens . discard_a_token ( ) ; // :
if ( ! tokens . next_token ( ) . is ( Token : : Type : : Ident ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. rule_name = " @page " _fly_string ,
. value_string = tokens . dump_string ( ) ,
. description = " Pseudo-classes must be idents. " _string ,
} ) ;
2025-05-16 11:37:53 +01:00
return ParseError : : SyntaxError ;
}
auto pseudo_class_name = static_cast < Token > ( tokens . consume_a_token ( ) ) . ident ( ) ;
if ( auto pseudo_class = page_pseudo_class_from_string ( pseudo_class_name ) ; pseudo_class . has_value ( ) ) {
pseudo_classes . append ( * pseudo_class ) ;
} else {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( UnknownPseudoClassOrElementError {
. rule_name = " @page " _fly_string ,
. name = MUST ( String : : formatted ( " :{} " , pseudo_class_name ) ) ,
} ) ;
2025-05-16 11:37:53 +01:00
return ParseError : : SyntaxError ;
}
}
if ( ! maybe_ident . has_value ( ) & & pseudo_classes . is_empty ( ) ) {
// Nothing parsed
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. rule_name = " @page " _fly_string ,
. value_string = tokens . dump_string ( ) ,
. description = " Is empty. " _string ,
} ) ;
2025-05-16 11:37:53 +01:00
return ParseError : : SyntaxError ;
}
selector_list . empend ( move ( maybe_ident ) , move ( pseudo_classes ) ) ;
tokens . discard_whitespace ( ) ;
if ( tokens . next_token ( ) . is ( Token : : Type : : Comma ) ) {
tokens . discard_a_token ( ) ; // ,
tokens . discard_whitespace ( ) ;
if ( ! tokens . has_next_token ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. rule_name = " @page " _fly_string ,
. value_string = tokens . dump_string ( ) ,
. description = " Trailing comma. " _string ,
} ) ;
2025-05-16 11:37:53 +01:00
return ParseError : : SyntaxError ;
}
} else if ( tokens . has_next_token ( ) ) {
2025-07-23 10:15:39 +01:00
ErrorReporter : : the ( ) . report ( InvalidSelectorError {
. rule_name = " @page " _fly_string ,
. value_string = tokens . dump_string ( ) ,
. description = " Trailing tokens. " _string ,
} ) ;
2025-05-16 11:37:53 +01:00
return ParseError : : SyntaxError ;
}
}
return selector_list ;
}
template Parser : : ParseErrorOr < PageSelectorList > Parser : : parse_a_page_selector_list ( TokenStream < ComponentValue > & ) ;
template Parser : : ParseErrorOr < PageSelectorList > Parser : : parse_a_page_selector_list ( TokenStream < Token > & ) ;
2023-08-17 15:18:41 +01:00
}