2020-01-18 09:38:21 +01:00
/*
* Copyright ( c ) 2018 - 2020 , Andreas Kling < kling @ serenityos . org >
2022-02-24 15:54:12 +00:00
* Copyright ( c ) 2021 - 2022 , Sam Atkins < atkinssj @ serenityos . org >
2020-01-18 09:38:21 +01:00
*
2021-04-22 01:24:48 -07:00
* SPDX - License - Identifier : BSD - 2 - Clause
2020-01-18 09:38:21 +01:00
*/
2021-05-08 23:19:25 +03:00
# include "Selector.h"
2021-10-15 12:18:09 +01:00
# include <LibWeb/CSS/Serialize.h>
2019-06-20 23:25:25 +02:00
2020-07-26 20:01:35 +02:00
namespace Web : : CSS {
2020-03-07 10:27:02 +01:00
2021-07-23 15:24:33 +01:00
Selector : : Selector ( Vector < CompoundSelector > & & compound_selectors )
: m_compound_selectors ( move ( compound_selectors ) )
2019-06-20 23:25:25 +02:00
{
2022-03-13 16:28:20 +01:00
// Note: This assumes that only one pseudo-element is allowed in a selector, and that it appears at the end.
// This is true currently, and there are no current proposals to change this, but you never know!
if ( ! m_compound_selectors . is_empty ( ) ) {
for ( auto const & simple_selector : m_compound_selectors . last ( ) . simple_selectors ) {
if ( simple_selector . type = = SimpleSelector : : Type : : PseudoElement ) {
m_pseudo_element = simple_selector . pseudo_element ;
break ;
}
}
}
2019-06-20 23:25:25 +02:00
}
2022-02-25 12:56:55 +00:00
// https://www.w3.org/TR/selectors-4/#specificity-rules
2020-06-25 16:43:49 +02:00
u32 Selector : : specificity ( ) const
2019-06-29 17:32:32 +02:00
{
2022-02-05 17:40:18 +02:00
if ( m_specificity . has_value ( ) )
return * m_specificity ;
2019-06-29 17:32:32 +02:00
unsigned ids = 0 ;
unsigned tag_names = 0 ;
unsigned classes = 0 ;
2021-07-23 15:24:33 +01:00
for ( auto & list : m_compound_selectors ) {
for ( auto & simple_selector : list . simple_selectors ) {
2019-11-27 20:37:36 +01:00
switch ( simple_selector . type ) {
case SimpleSelector : : Type : : Id :
+ + ids ;
break ;
case SimpleSelector : : Type : : Class :
2022-02-25 12:56:55 +00:00
case SimpleSelector : : Type : : Attribute :
case SimpleSelector : : Type : : PseudoClass :
2019-11-27 20:37:36 +01:00
+ + classes ;
break ;
case SimpleSelector : : Type : : TagName :
2022-02-25 12:56:55 +00:00
case SimpleSelector : : Type : : PseudoElement :
2019-11-27 20:37:36 +01:00
+ + tag_names ;
break ;
default :
break ;
}
2019-06-29 17:32:32 +02:00
}
}
2022-02-05 17:40:18 +02:00
m_specificity = ids * 0x10000 + classes * 0x100 + tag_names ;
return * m_specificity ;
2019-06-29 17:32:32 +02:00
}
2020-03-07 10:27:02 +01:00
2021-10-15 11:30:01 +01:00
// https://www.w3.org/TR/cssom/#serialize-a-simple-selector
String Selector : : SimpleSelector : : serialize ( ) const
{
StringBuilder s ;
switch ( type ) {
case Selector : : SimpleSelector : : Type : : TagName :
case Selector : : SimpleSelector : : Type : : Universal :
// FIXME: 1. If the namespace prefix maps to a namespace that is not the default namespace and is not the null namespace (not in a namespace) append the serialization of the namespace prefix as an identifier, followed by a "|" (U+007C) to s.
// FIXME: 2. If the namespace prefix maps to a namespace that is the null namespace (not in a namespace) append "|" (U+007C) to s.
// 3. If this is a type selector append the serialization of the element name as an identifier to s.
if ( type = = Selector : : SimpleSelector : : Type : : TagName ) {
2021-10-15 12:18:09 +01:00
serialize_an_identifier ( s , value ) ;
2021-10-15 11:30:01 +01:00
}
// 4. If this is a universal selector append "*" (U+002A) to s.
if ( type = = Selector : : SimpleSelector : : Type : : Universal )
s . append ( ' * ' ) ;
break ;
case Selector : : SimpleSelector : : Type : : Attribute :
// 1. Append "[" (U+005B) to s.
s . append ( ' [ ' ) ;
// FIXME: 2. If the namespace prefix maps to a namespace that is not the null namespace (not in a namespace) append the serialization of the namespace prefix as an identifier, followed by a "|" (U+007C) to s.
// 3. Append the serialization of the attribute name as an identifier to s.
2021-10-15 12:18:09 +01:00
serialize_an_identifier ( s , attribute . name ) ;
2021-10-15 11:30:01 +01:00
// 4. If there is an attribute value specified, append "=", "~=", "|=", "^=", "$=", or "*=" as appropriate (depending on the type of attribute selector),
// followed by the serialization of the attribute value as a string, to s.
if ( ! attribute . value . is_null ( ) ) {
switch ( attribute . match_type ) {
case Selector : : SimpleSelector : : Attribute : : MatchType : : ExactValueMatch :
s . append ( " = " ) ;
break ;
case Selector : : SimpleSelector : : Attribute : : MatchType : : ContainsWord :
s . append ( " ~= " ) ;
break ;
case Selector : : SimpleSelector : : Attribute : : MatchType : : ContainsString :
s . append ( " *= " ) ;
break ;
case Selector : : SimpleSelector : : Attribute : : MatchType : : StartsWithSegment :
s . append ( " |= " ) ;
break ;
case Selector : : SimpleSelector : : Attribute : : MatchType : : StartsWithString :
s . append ( " ^= " ) ;
break ;
case Selector : : SimpleSelector : : Attribute : : MatchType : : EndsWithString :
s . append ( " $= " ) ;
break ;
default :
break ;
}
2021-10-15 12:18:09 +01:00
serialize_a_string ( s , attribute . value ) ;
2021-10-15 11:30:01 +01:00
}
// FIXME: 5. If the attribute selector has the case-sensitivity flag present, append " i" (U+0020 U+0069) to s.
// 6. Append "]" (U+005D) to s.
s . append ( ' ] ' ) ;
break ;
case Selector : : SimpleSelector : : Type : : Class :
// Append a "." (U+002E), followed by the serialization of the class name as an identifier to s.
s . append ( ' . ' ) ;
2021-10-15 12:18:09 +01:00
serialize_an_identifier ( s , value ) ;
2021-10-15 11:30:01 +01:00
break ;
case Selector : : SimpleSelector : : Type : : Id :
// Append a "#" (U+0023), followed by the serialization of the ID as an identifier to s.
s . append ( ' # ' ) ;
2021-10-15 12:18:09 +01:00
serialize_an_identifier ( s , value ) ;
2021-10-15 11:30:01 +01:00
break ;
case Selector : : SimpleSelector : : Type : : PseudoClass :
switch ( pseudo_class . type ) {
case Selector : : SimpleSelector : : PseudoClass : : Type : : Link :
case Selector : : SimpleSelector : : PseudoClass : : Type : : Visited :
case Selector : : SimpleSelector : : PseudoClass : : Type : : Hover :
case Selector : : SimpleSelector : : PseudoClass : : Type : : Focus :
case Selector : : SimpleSelector : : PseudoClass : : Type : : FirstChild :
case Selector : : SimpleSelector : : PseudoClass : : Type : : LastChild :
case Selector : : SimpleSelector : : PseudoClass : : Type : : OnlyChild :
case Selector : : SimpleSelector : : PseudoClass : : Type : : Empty :
case Selector : : SimpleSelector : : PseudoClass : : Type : : Root :
case Selector : : SimpleSelector : : PseudoClass : : Type : : FirstOfType :
case Selector : : SimpleSelector : : PseudoClass : : Type : : LastOfType :
2022-02-17 22:43:22 +01:00
case Selector : : SimpleSelector : : PseudoClass : : Type : : OnlyOfType :
2021-10-15 11:30:01 +01:00
case Selector : : SimpleSelector : : PseudoClass : : Type : : Disabled :
case Selector : : SimpleSelector : : PseudoClass : : Type : : Enabled :
case Selector : : SimpleSelector : : PseudoClass : : Type : : Checked :
case Selector : : SimpleSelector : : PseudoClass : : Type : : Active :
// If the pseudo-class does not accept arguments append ":" (U+003A), followed by the name of the pseudo-class, to s.
s . append ( ' : ' ) ;
s . append ( pseudo_class_name ( pseudo_class . type ) ) ;
break ;
case Selector : : SimpleSelector : : PseudoClass : : Type : : NthChild :
case Selector : : SimpleSelector : : PseudoClass : : Type : : NthLastChild :
case Selector : : SimpleSelector : : PseudoClass : : Type : : Not :
2022-03-17 15:28:42 +00:00
case Selector : : SimpleSelector : : PseudoClass : : Type : : Is :
2022-03-17 16:13:13 +00:00
case Selector : : SimpleSelector : : PseudoClass : : Type : : Where :
2021-10-15 11:30:01 +01:00
// Otherwise, append ":" (U+003A), followed by the name of the pseudo-class, followed by "(" (U+0028),
// followed by the value of the pseudo-class argument(s) determined as per below, followed by ")" (U+0029), to s.
s . append ( ' : ' ) ;
s . append ( pseudo_class_name ( pseudo_class . type ) ) ;
s . append ( ' ( ' ) ;
if ( pseudo_class . type = = Selector : : SimpleSelector : : PseudoClass : : Type : : NthChild
| | pseudo_class . type = = Selector : : SimpleSelector : : PseudoClass : : Type : : NthLastChild ) {
2021-10-15 12:18:09 +01:00
// The result of serializing the value using the rules to serialize an <an+b> value.
s . append ( pseudo_class . nth_child_pattern . serialize ( ) ) ;
2022-03-17 15:28:42 +00:00
} else if ( pseudo_class . type = = Selector : : SimpleSelector : : PseudoClass : : Type : : Not
2022-03-17 16:13:13 +00:00
| | pseudo_class . type = = Selector : : SimpleSelector : : PseudoClass : : Type : : Is
| | pseudo_class . type = = Selector : : SimpleSelector : : PseudoClass : : Type : : Where ) {
2021-10-15 11:30:01 +01:00
// The result of serializing the value using the rules for serializing a group of selectors.
2022-03-17 16:13:13 +00:00
// NOTE: `:is()` and `:where()` aren't in the spec for this yet, but it should be!
2022-03-17 15:28:42 +00:00
s . append ( serialize_a_group_of_selectors ( pseudo_class . argument_selector_list ) ) ;
2021-10-15 11:30:01 +01:00
}
s . append ( ' ) ' ) ;
break ;
default :
VERIFY_NOT_REACHED ( ) ;
}
break ;
2022-02-25 12:56:55 +00:00
case Selector : : SimpleSelector : : Type : : PseudoElement :
// Note: Pseudo-elements are dealt with in Selector::serialize()
break ;
2021-10-15 11:30:01 +01:00
default :
dbgln ( " FIXME: Unsupported simple selector serialization for type {} " , to_underlying ( type ) ) ;
break ;
}
return s . to_string ( ) ;
}
// https://www.w3.org/TR/cssom/#serialize-a-selector
String Selector : : serialize ( ) const
{
StringBuilder s ;
// To serialize a selector let s be the empty string, run the steps below for each part of the chain of the selector, and finally return s:
for ( size_t i = 0 ; i < compound_selectors ( ) . size ( ) ; + + i ) {
auto const & compound_selector = compound_selectors ( ) [ i ] ;
// 1. If there is only one simple selector in the compound selectors which is a universal selector, append the result of serializing the universal selector to s.
if ( compound_selector . simple_selectors . size ( ) = = 1
& & compound_selector . simple_selectors . first ( ) . type = = Selector : : SimpleSelector : : Type : : Universal ) {
2022-03-13 13:16:48 +01:00
s . append ( compound_selector . simple_selectors . first ( ) . serialize ( ) ) ;
2021-10-15 11:30:01 +01:00
}
// 2. Otherwise, for each simple selector in the compound selectors...
// FIXME: ...that is not a universal selector of which the namespace prefix maps to a namespace that is not the default namespace...
// ...serialize the simple selector and append the result to s.
else {
for ( auto & simple_selector : compound_selector . simple_selectors ) {
s . append ( simple_selector . serialize ( ) ) ;
}
}
// 3. If this is not the last part of the chain of the selector append a single SPACE (U+0020),
// followed by the combinator ">", "+", "~", ">>", "||", as appropriate, followed by another
// single SPACE (U+0020) if the combinator was not whitespace, to s.
if ( i ! = compound_selectors ( ) . size ( ) - 1 ) {
s . append ( ' ' ) ;
2021-10-15 11:51:54 +01:00
// Note: The combinator that appears between parts `i` and `i+1` appears with the `i+1` selector,
// so we have to check that one.
switch ( compound_selectors ( ) [ i + 1 ] . combinator ) {
2021-10-15 11:30:01 +01:00
case Selector : : Combinator : : ImmediateChild :
2021-10-15 11:51:54 +01:00
s . append ( " > " ) ;
2021-10-15 11:30:01 +01:00
break ;
case Selector : : Combinator : : NextSibling :
2021-10-15 11:51:54 +01:00
s . append ( " + " ) ;
2021-10-15 11:30:01 +01:00
break ;
case Selector : : Combinator : : SubsequentSibling :
2021-10-15 11:51:54 +01:00
s . append ( " ~ " ) ;
2021-10-15 11:30:01 +01:00
break ;
case Selector : : Combinator : : Column :
2021-10-15 11:51:54 +01:00
s . append ( " || " ) ;
2021-10-15 11:30:01 +01:00
break ;
default :
break ;
}
} else {
2021-10-15 11:53:23 +01:00
// 4. If this is the last part of the chain of the selector and there is a pseudo-element,
// append "::" followed by the name of the pseudo-element, to s.
if ( compound_selector . simple_selectors . last ( ) . type = = Selector : : SimpleSelector : : Type : : PseudoElement ) {
2021-10-15 11:30:01 +01:00
s . append ( " :: " ) ;
2021-10-15 11:53:23 +01:00
s . append ( pseudo_element_name ( compound_selector . simple_selectors . last ( ) . pseudo_element ) ) ;
2021-10-15 11:30:01 +01:00
}
}
}
return s . to_string ( ) ;
}
// https://www.w3.org/TR/cssom/#serialize-a-group-of-selectors
String serialize_a_group_of_selectors ( NonnullRefPtrVector < Selector > const & selectors )
{
// To serialize a group of selectors serialize each selector in the group of selectors and then serialize a comma-separated list of these serializations.
StringBuilder builder ;
2021-10-15 11:57:13 +01:00
builder . join ( " , " , selectors ) ;
2021-10-15 11:30:01 +01:00
return builder . to_string ( ) ;
}
2022-03-03 14:22:50 +00:00
Optional < Selector : : PseudoElement > pseudo_element_from_string ( StringView name )
{
if ( name . equals_ignoring_case ( " after " ) ) {
return Selector : : PseudoElement : : After ;
} else if ( name . equals_ignoring_case ( " before " ) ) {
return Selector : : PseudoElement : : Before ;
} else if ( name . equals_ignoring_case ( " first-letter " ) ) {
return Selector : : PseudoElement : : FirstLetter ;
} else if ( name . equals_ignoring_case ( " first-line " ) ) {
return Selector : : PseudoElement : : FirstLine ;
} else if ( name . equals_ignoring_case ( " marker " ) ) {
return Selector : : PseudoElement : : Marker ;
}
return { } ;
}
2020-03-07 10:27:02 +01:00
}