float_pigment_css/sheet/
selector.rs

1use alloc::{
2    boxed::Box,
3    string::{String, ToString},
4    vec::Vec,
5};
6use core::{cell::Cell, fmt, num::NonZeroUsize};
7
8use cssparser::{Parser, ParserInput};
9#[cfg(debug_assertions)]
10use float_pigment_css_macro::{compatibility_enum_check, compatibility_struct_check};
11
12use crate::parser::{parse_selector, ParseState};
13use crate::query::{StyleNode, StyleNodeAttributeCaseSensitivity, StyleNodeClass};
14
15#[cfg_attr(debug_assertions, compatibility_enum_check(selector))]
16#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
17pub(crate) enum SelectorRelationType {
18    None,
19    Ancestor(SelectorFragment),
20    DirectParent(SelectorFragment),
21    NextSibling(SelectorFragment),
22    SubsequentSibling(SelectorFragment),
23}
24
25#[cfg_attr(debug_assertions, compatibility_enum_check(selector))]
26#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
27#[allow(clippy::box_collection)] // TODO optimize here
28pub(crate) enum PseudoClasses {
29    Host,
30    FirstChild,
31    LastChild,
32    Empty,
33    Not(Vec<SelectorFragment>),
34    OnlyChild,
35    NthChild(i32, i32, Option<Box<Vec<SelectorFragment>>>),
36    NthOfType(i32, i32),
37}
38
39impl core::fmt::Display for PseudoClasses {
40    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
41        let s = match self {
42            Self::Host => "host".to_string(),
43            Self::FirstChild => "first-child".to_string(),
44            Self::LastChild => "last-child".to_string(),
45            Self::Empty => "empty".to_string(),
46            Self::Not(selectors) => {
47                let selectors_str = selectors
48                    .iter()
49                    .map(|x| x.to_string())
50                    .collect::<Vec<String>>()
51                    .join(", ");
52                format!("not({})", selectors_str)
53            }
54            Self::OnlyChild => "only-child".to_string(),
55            Self::NthChild(a, b, selector_list) => {
56                if let Some(selectors) = selector_list {
57                    format!(
58                        "nth-child({}n + {} of {})",
59                        a,
60                        b,
61                        selectors
62                            .iter()
63                            .map(|selector| selector.to_string())
64                            .collect::<Vec<String>>()
65                            .join(",")
66                    )
67                } else {
68                    format!("nth-child({}n + {})", a, b)
69                }
70            }
71            Self::NthOfType(a, b) => format!("nth-of-type({}n + {})", a, b),
72        };
73        write!(f, "{}", s)
74    }
75}
76
77/// The CSS pseudo-elements.
78#[cfg_attr(debug_assertions, compatibility_enum_check(selector))]
79#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
80pub enum PseudoElements {
81    /// The `::before` pseudo-element.
82    Before,
83    /// The `::after` pseudo-element.
84    After,
85    /// The `::selection` pseudo-element.
86    Selection,
87}
88
89impl core::fmt::Display for PseudoElements {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        let s = match self {
92            Self::Before => "before",
93            Self::After => "after",
94            Self::Selection => "selection",
95        };
96        write!(f, "{}", s)
97    }
98}
99
100pub(crate) static SELECTOR_WHITESPACE: &[char] = &[' ', '\t', '\n', '\r', '\x0C'];
101
102#[repr(C)]
103#[cfg_attr(debug_assertions, compatibility_enum_check(selector))]
104#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
105#[allow(clippy::enum_variant_names)]
106pub(crate) enum AttributeFlags {
107    CaseSensitivityDependsOnName, // no flag
108    CaseSensitive,                // 's' flag
109    CaseInsensitive,              // 'i' flag
110}
111
112#[repr(C)]
113#[cfg_attr(debug_assertions, compatibility_enum_check(selector))]
114#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
115pub(crate) enum AttributeOperator {
116    Set,
117    Exact,
118    List,
119    Hyphen,
120    Begin,
121    End,
122    Contain,
123}
124
125#[cfg_attr(debug_assertions, compatibility_struct_check(selector))]
126#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
127pub(crate) struct Attribute {
128    pub(crate) operator: AttributeOperator,
129    pub(crate) case_insensitive: AttributeFlags,
130    pub(crate) never_matches: bool,
131    pub(crate) name: String,
132    pub(crate) value: Option<String>,
133}
134
135impl Attribute {
136    pub(crate) fn new_set(name: String) -> Self {
137        Self {
138            operator: AttributeOperator::Set,
139            case_insensitive: AttributeFlags::CaseSensitivityDependsOnName,
140            never_matches: false,
141            name,
142            value: None,
143        }
144    }
145}
146
147impl core::fmt::Display for Attribute {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        let mut s = self.name.to_string();
150        match self.operator {
151            AttributeOperator::Set => {
152                return write!(f, "{}", s);
153            }
154            AttributeOperator::Exact => s.push('='),
155            AttributeOperator::List => s.push_str("~="),
156            AttributeOperator::Begin => s.push_str("^="),
157            AttributeOperator::End => s.push_str("$="),
158            AttributeOperator::Contain => s.push_str("*="),
159            AttributeOperator::Hyphen => s.push_str("|="),
160        };
161        s.push_str(&format!("\"{}\"", self.value.as_ref().unwrap()));
162        match self.case_insensitive {
163            AttributeFlags::CaseInsensitive => s.push_str(" i"),
164            AttributeFlags::CaseSensitive => s.push_str(" s"),
165            AttributeFlags::CaseSensitivityDependsOnName => {}
166        }
167        write!(f, "{}", s)
168    }
169}
170
171// TODO consider change String to StrRef
172#[cfg_attr(debug_assertions, compatibility_struct_check(selector))]
173#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
174#[allow(clippy::box_collection)] // TODO optimize here
175pub(crate) struct SelectorFragment {
176    pub(crate) tag_name: String,
177    pub(crate) id: String,
178    pub(crate) classes: Vec<String>,
179    pub(crate) relation: Option<Box<SelectorRelationType>>,
180    weight: Cell<u16>,
181    pub(crate) pseudo_classes: Option<Box<PseudoClasses>>,
182    pub(crate) pseudo_elements: Option<Box<PseudoElements>>,
183    pub(crate) attributes: Option<Box<Vec<Attribute>>>,
184}
185
186impl fmt::Display for SelectorFragment {
187    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
188        if let Some(relation) = &self.relation {
189            match &**relation {
190                SelectorRelationType::None => {}
191                SelectorRelationType::Ancestor(x) => write!(f, "{} ", x)?,
192                SelectorRelationType::DirectParent(x) => write!(f, "{} > ", x)?,
193                SelectorRelationType::NextSibling(x) => write!(f, "{} + ", x)?,
194                SelectorRelationType::SubsequentSibling(x) => write!(f, "{} ~ ", x)?,
195            }
196        }
197        if !self.tag_name.is_empty() {
198            write!(f, "{}", self.tag_name)?;
199        }
200        if !self.id.is_empty() {
201            write!(f, "#{}", self.id)?;
202        }
203        for class in self.classes.iter() {
204            write!(f, ".{}", class)?;
205        }
206        if self.pseudo_classes.is_some() {
207            write!(f, ":{}", self.pseudo_classes.as_ref().unwrap())?;
208        }
209        if self.pseudo_elements.is_some() {
210            write!(f, "::{}", self.pseudo_elements.as_ref().unwrap())?;
211        }
212        if let Some(attributes) = self.attributes.as_ref() {
213            for attr in attributes.iter() {
214                write!(f, "[{}]", attr)?;
215            }
216        }
217        Ok(())
218    }
219}
220
221impl SelectorFragment {
222    pub(crate) fn new() -> Self {
223        Self {
224            tag_name: String::new(),
225            id: String::new(),
226            classes: vec![],
227            relation: None,
228            weight: Cell::new(0),
229            pseudo_classes: None,
230            pseudo_elements: None,
231            attributes: None,
232        }
233    }
234    pub(crate) fn with_relation(parent: SelectorRelationType) -> Self {
235        Self {
236            tag_name: String::new(),
237            id: String::new(),
238            classes: vec![],
239            relation: Some(Box::new(parent)),
240            weight: Cell::new(0),
241            pseudo_classes: None,
242            pseudo_elements: None,
243            attributes: None,
244        }
245    }
246    pub(crate) fn weight(&self) -> u16 {
247        let mut weight = self.weight.get();
248        if weight > 0 {
249            return weight;
250        }
251        if !self.tag_name.is_empty() {
252            weight += 1 << 0;
253        }
254        if !self.id.is_empty() {
255            weight += 1 << 13;
256        }
257        let class_and_attr_count = self.classes.len()
258            + self
259                .attributes
260                .as_ref()
261                .map(|a| a.len())
262                .unwrap_or_default();
263        weight += (class_and_attr_count.min(0xff) << 5) as u16;
264        if let Some(ref relation) = self.relation {
265            weight += match &**relation {
266                SelectorRelationType::None => 0,
267                SelectorRelationType::Ancestor(x) => x.weight(),
268                SelectorRelationType::DirectParent(x) => x.weight(),
269                SelectorRelationType::NextSibling(x) => x.weight(),
270                SelectorRelationType::SubsequentSibling(x) => x.weight(),
271            }
272        }
273        if self.pseudo_classes.as_ref().is_some() {
274            weight += 1 << 5;
275        }
276        self.weight.set(weight);
277        weight
278    }
279    pub(crate) fn set_tag_name(&mut self, tag_name: &str) {
280        self.tag_name = tag_name.into();
281    }
282    pub(crate) fn set_id(&mut self, id: &str) {
283        self.id = id.into();
284    }
285    pub(crate) fn add_class(&mut self, class: &str) {
286        self.classes.push(class.into())
287    }
288    #[cfg(feature = "deserialize")]
289    pub(crate) fn set_basics(&mut self, tag_name: String, id: String, classes: Vec<String>) {
290        self.tag_name = tag_name;
291        self.id = id;
292        self.classes = classes;
293    }
294    pub(crate) fn set_pseudo_classes(&mut self, pseudo_classes: PseudoClasses) {
295        self.pseudo_classes = Some(Box::new(pseudo_classes));
296    }
297    pub(crate) fn set_pseudo_elements(&mut self, pseudo_elements: PseudoElements) {
298        self.pseudo_elements = Some(Box::new(pseudo_elements));
299    }
300    pub(crate) fn add_attribute(&mut self, attribute: Attribute) {
301        if self.attributes.is_none() {
302            self.attributes.replace(Box::new(Vec::with_capacity(1)));
303        }
304        if let Some(ref mut attributes) = self.attributes {
305            attributes.push(attribute);
306        }
307    }
308    pub(crate) fn add_tag_name_prefix(&mut self, prefix: &str) {
309        if !self.tag_name.is_empty() {
310            self.tag_name = format!("{}{}", prefix, self.tag_name);
311        }
312        if let Some(parent) = self.relation.as_mut() {
313            match parent.as_mut() {
314                SelectorRelationType::Ancestor(frag) => {
315                    frag.add_tag_name_prefix(prefix);
316                }
317                SelectorRelationType::DirectParent(frag) => {
318                    frag.add_tag_name_prefix(prefix);
319                }
320                _ => {}
321            }
322        }
323    }
324}
325
326#[cfg_attr(debug_assertions, compatibility_struct_check(selector))]
327#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Default)]
328pub(crate) struct Selector {
329    pub(crate) fragments: Vec<SelectorFragment>,
330    pub(crate) max_weight: u16,
331}
332
333impl fmt::Display for Selector {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        write!(
336            f,
337            "{}",
338            self.fragments
339                .iter()
340                .map(|f| f.to_string())
341                .collect::<Box<[String]>>()
342                .join(", ")
343        )
344    }
345}
346
347impl Selector {
348    pub(crate) fn star_selector() -> Self {
349        Self {
350            fragments: vec![SelectorFragment::new()],
351            max_weight: 0,
352        }
353    }
354    pub(crate) fn from_fragments(fragments: Vec<SelectorFragment>) -> Self {
355        let mut max_weight = 0;
356        for f in fragments.iter() {
357            let w = f.weight();
358            if w > max_weight {
359                max_weight = w;
360            }
361        }
362        Self {
363            fragments,
364            max_weight,
365        }
366    }
367    pub(crate) fn get_index_classes(&self) -> Vec<String> {
368        let mut ret = vec![];
369        for frag in self.fragments.iter() {
370            let s = if !frag.classes.is_empty() {
371                frag.classes[0].clone()
372            } else {
373                String::new()
374            };
375            if !ret.contains(&s) {
376                ret.push(s)
377            }
378        }
379        ret
380    }
381    pub(crate) fn match_query<T: StyleNode>(
382        &self,
383        query: &[T],
384        sheet_style_scope: Option<NonZeroUsize>,
385    ) -> Option<u16> {
386        let mut cur_weight = 0;
387        'f: for frag in self.fragments.iter() {
388            let mut query = query.iter();
389            match query.next_back() {
390                Some(mut cur_query) => {
391                    let same_scope = sheet_style_scope.is_none()
392                        || sheet_style_scope == cur_query.style_scope()
393                        || sheet_style_scope == cur_query.extra_style_scope();
394                    let mut allow_ancestor = false;
395                    let mut cur_frag = frag;
396                    loop {
397                        let mut matches = true;
398
399                        // fails if id/tag_name not matching
400                        if (!cur_frag.id.is_empty()
401                            && (!same_scope || Some(cur_frag.id.as_str()) != cur_query.id()))
402                            || (!cur_frag.tag_name.is_empty()
403                                && (!same_scope || cur_frag.tag_name != cur_query.tag_name()))
404                        {
405                            matches = false
406                        }
407
408                        // fails if pseudo classes not matching
409                        if matches {
410                            if let Some(pc) = cur_frag.pseudo_classes.as_ref() {
411                                // currently we only check `:host`
412                                match &**pc {
413                                    PseudoClasses::Host => {
414                                        if sheet_style_scope.is_some()
415                                            && sheet_style_scope != cur_query.host_style_scope()
416                                        {
417                                            matches = false
418                                        }
419                                    }
420                                    _ => matches = false,
421                                }
422                            }
423                        }
424
425                        // fails if pseudo classes not matching
426                        if matches {
427                            let pc = cur_frag.pseudo_elements.as_ref().map(|x| (**x).clone());
428                            if pc != cur_query.pseudo_element() {
429                                matches = false
430                            }
431                        }
432
433                        // fails if any class not matching
434                        if matches {
435                            for class_name in cur_frag.classes.iter() {
436                                if !cur_query.classes().any(|x| {
437                                    (sheet_style_scope.is_none() || sheet_style_scope == x.scope())
438                                        && x.name() == class_name
439                                }) {
440                                    matches = false;
441                                }
442                            }
443                        }
444
445                        // fails if any attribute not matching
446                        if matches {
447                            if let Some(selector_attributes) = &cur_frag.attributes {
448                                for attribute in selector_attributes.iter() {
449                                    let selector_attr_value =
450                                        attribute.value.as_deref().unwrap_or_default();
451                                    if let Some((element_attr_value, sensitivity)) =
452                                        cur_query.attribute(&attribute.name)
453                                    {
454                                        let sensitivity = match (
455                                            &attribute.case_insensitive,
456                                            sensitivity,
457                                        ) {
458                                            (AttributeFlags::CaseInsensitive, _)
459                                            | (
460                                                AttributeFlags::CaseSensitivityDependsOnName,
461                                                StyleNodeAttributeCaseSensitivity::CaseInsensitive,
462                                            ) => StyleNodeAttributeCaseSensitivity::CaseInsensitive,
463                                            (AttributeFlags::CaseSensitive, _)
464                                            | (
465                                                AttributeFlags::CaseSensitivityDependsOnName,
466                                                StyleNodeAttributeCaseSensitivity::CaseSensitive,
467                                            ) => StyleNodeAttributeCaseSensitivity::CaseSensitive,
468                                        };
469                                        if !match attribute.operator {
470                                            AttributeOperator::Set => true,
471                                            AttributeOperator::Exact => sensitivity
472                                                .eq(element_attr_value, selector_attr_value),
473                                            AttributeOperator::List => {
474                                                if selector_attr_value.is_empty() {
475                                                    false
476                                                } else {
477                                                    element_attr_value
478                                                        .split(SELECTOR_WHITESPACE)
479                                                        .any(|x| {
480                                                            sensitivity.eq(x, selector_attr_value)
481                                                        })
482                                                }
483                                            }
484                                            AttributeOperator::Hyphen => {
485                                                #[allow(clippy::comparison_chain)]
486                                                if element_attr_value.len()
487                                                    < selector_attr_value.len()
488                                                {
489                                                    false
490                                                } else if element_attr_value.len()
491                                                    == selector_attr_value.len()
492                                                {
493                                                    element_attr_value == selector_attr_value
494                                                } else {
495                                                    sensitivity.starts_with(
496                                                        element_attr_value,
497                                                        &alloc::format!("{}-", selector_attr_value),
498                                                    )
499                                                }
500                                            }
501                                            AttributeOperator::Begin => sensitivity.starts_with(
502                                                element_attr_value,
503                                                selector_attr_value,
504                                            ),
505                                            AttributeOperator::End => sensitivity
506                                                .ends_with(element_attr_value, selector_attr_value),
507                                            AttributeOperator::Contain => sensitivity
508                                                .contains(element_attr_value, selector_attr_value),
509                                        } {
510                                            matches = false;
511                                            break;
512                                        }
513                                    } else {
514                                        matches = false;
515                                        break;
516                                    }
517                                }
518                            }
519                        }
520
521                        // check ancestors if not matches
522                        if !matches {
523                            if allow_ancestor {
524                                cur_query = match query.next_back() {
525                                    Some(x) => x,
526                                    None => continue 'f,
527                                }
528                            } else {
529                                continue 'f;
530                            }
531                            continue;
532                        }
533
534                        // check ancestors if the rule requires
535                        if let Some(ref relation) = cur_frag.relation {
536                            cur_query = match query.next_back() {
537                                Some(x) => x,
538                                None => continue 'f,
539                            };
540                            match &**relation {
541                                SelectorRelationType::None => {
542                                    // empty
543                                }
544                                SelectorRelationType::Ancestor(x) => {
545                                    cur_frag = x;
546                                    allow_ancestor = true;
547                                }
548                                SelectorRelationType::DirectParent(x) => {
549                                    cur_frag = x;
550                                    allow_ancestor = false;
551                                }
552                                SelectorRelationType::NextSibling(x) => {
553                                    cur_frag = x;
554                                    allow_ancestor = false;
555                                }
556                                SelectorRelationType::SubsequentSibling(x) => {
557                                    cur_frag = x;
558                                    allow_ancestor = false
559                                }
560                            }
561                        } else {
562                            break;
563                        }
564                    }
565                }
566                None => break,
567            }
568            let w = frag.weight();
569            if w == self.max_weight {
570                return Some(w);
571            }
572            cur_weight = w;
573        }
574        if cur_weight > 0 {
575            Some(cur_weight)
576        } else {
577            None
578        }
579    }
580    pub(crate) fn from_string(selector_str: &str) -> Selector {
581        let mut parser_input = ParserInput::new(selector_str);
582        let mut parser = Parser::new(&mut parser_input);
583        let mut st = ParseState::new(None, crate::parser::StyleParsingDebugMode::None, None);
584        let selector = parse_selector(&mut parser, &mut st);
585        if let Ok(ret) = selector {
586            return ret;
587        }
588        Selector::default()
589    }
590}