diff --git a/crates/gosub_css3/src/convert/ast_converter.rs b/crates/gosub_css3/src/convert/ast_converter.rs index 135da4711..3ea8436fa 100644 --- a/crates/gosub_css3/src/convert/ast_converter.rs +++ b/crates/gosub_css3/src/convert/ast_converter.rs @@ -90,7 +90,9 @@ pub fn convert_ast_to_stylesheet( continue; } - let mut selector = CssSelector { parts: vec![] }; + let mut selector = CssSelector { + parts: vec![vec![]], + }; for node in node.as_selector_list().iter() { if !node.is_selector() { continue; @@ -134,11 +136,19 @@ pub fn convert_ast_to_stylesheet( value: value.clone(), case_insensitive: flags.eq_ignore_ascii_case("i"), })), + NodeType::Comma => { + selector.parts.push(vec![]); + continue; + } _ => { return Err(anyhow!("Unsupported selector part: {:?}", node.node_type)) } }; - selector.parts.push(part); + if let Some(x) = selector.parts.last_mut() { + x.push(part) + } else { + selector.parts.push(vec![part]); //unreachable, but still, we handle it + } } } rule.selectors.push(selector); diff --git a/crates/gosub_css3/src/parser/selector.rs b/crates/gosub_css3/src/parser/selector.rs index 3a446a308..0de3d76a0 100644 --- a/crates/gosub_css3/src/parser/selector.rs +++ b/crates/gosub_css3/src/parser/selector.rs @@ -255,11 +255,22 @@ impl Css3<'_> { let mut space = false; let mut whitespace_location = loc.clone(); + let mut skip_space = false; + while !self.tokenizer.eof() { let t = self.consume_any()?; if t.is_comment() { continue; } + + if skip_space { + if t.is_whitespace() { + continue; + } else { + skip_space = false; + } + } + if t.is_whitespace() { // on whitespace for selector whitespace_location = t.location.clone(); @@ -324,6 +335,11 @@ impl Css3<'_> { self.tokenizer.reconsume(); self.parse_nesting_selector()? } + TokenType::Comma => { + skip_space = true; + + Node::new(NodeType::Comma, t.location) + } _ => { self.tokenizer.reconsume(); break; diff --git a/crates/gosub_css3/src/stylesheet.rs b/crates/gosub_css3/src/stylesheet.rs index 14f75fce0..b1fc3fa8c 100644 --- a/crates/gosub_css3/src/stylesheet.rs +++ b/crates/gosub_css3/src/stylesheet.rs @@ -61,30 +61,16 @@ pub struct CssDeclaration { #[derive(Debug, PartialEq, Clone)] pub struct CssSelector { // List of parts that make up this selector - pub parts: Vec, + pub parts: Vec>, } impl CssSelector { /// Generate specificity for this selector - pub fn specificity(&self) -> Specificity { - let mut id_count = 0; - let mut class_count = 0; - let mut element_count = 0; - for part in &self.parts { - match part { - CssSelectorPart::Id(_) => { - id_count += 1; - } - CssSelectorPart::Class(_) => { - class_count += 1; - } - CssSelectorPart::Type(_) => { - element_count += 1; - } - _ => {} - } - } - Specificity::new(id_count, class_count, element_count) + pub fn specificity(&self) -> Vec { + self.parts + .iter() + .map(|part| Specificity::from(part.as_slice())) + .collect() } } @@ -219,6 +205,29 @@ impl Specificity { } } +impl From<&[CssSelectorPart]> for Specificity { + fn from(parts: &[CssSelectorPart]) -> Self { + let mut id_count = 0; + let mut class_count = 0; + let mut element_count = 0; + for part in parts { + match part { + CssSelectorPart::Id(_) => { + id_count += 1; + } + CssSelectorPart::Class(_) => { + class_count += 1; + } + CssSelectorPart::Type(_) => { + element_count += 1; + } + _ => {} + } + } + Specificity::new(id_count, class_count, element_count) + } +} + impl PartialOrd for Specificity { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) @@ -429,6 +438,7 @@ impl CssValue { #[cfg(test)] mod test { use super::*; + use std::vec; // #[test] // fn test_css_value_to_color() { @@ -448,7 +458,7 @@ mod test { fn test_css_rule() { let rule = CssRule { selectors: vec![CssSelector { - parts: vec![CssSelectorPart::Type("h1".to_string())], + parts: vec![vec![CssSelectorPart::Type("h1".to_string())]], }], declarations: vec![CssDeclaration { property: "color".to_string(), @@ -458,7 +468,15 @@ mod test { }; assert_eq!(rule.selectors().len(), 1); - let part = rule.selectors().first().unwrap().parts.first().unwrap(); + let part = rule + .selectors() + .first() + .unwrap() + .parts + .first() + .unwrap() + .first() + .unwrap(); assert_eq!(part, &CssSelectorPart::Type("h1".to_string())); assert_eq!(rule.declarations().len(), 1); @@ -468,42 +486,42 @@ mod test { #[test] fn test_specificity() { let selector = CssSelector { - parts: vec![ + parts: vec![vec![ CssSelectorPart::Type("h1".to_string()), CssSelectorPart::Class("myclass".to_string()), CssSelectorPart::Id("myid".to_string()), - ], + ]], }; let specificity = selector.specificity(); - assert_eq!(specificity, Specificity::new(1, 1, 1)); + assert_eq!(specificity, vec![Specificity::new(1, 1, 1)]); let selector = CssSelector { - parts: vec![ + parts: vec![vec![ CssSelectorPart::Type("h1".to_string()), CssSelectorPart::Class("myclass".to_string()), - ], + ]], }; let specificity = selector.specificity(); - assert_eq!(specificity, Specificity::new(0, 1, 1)); + assert_eq!(specificity, vec![Specificity::new(0, 1, 1)]); let selector = CssSelector { - parts: vec![CssSelectorPart::Type("h1".to_string())], + parts: vec![vec![CssSelectorPart::Type("h1".to_string())]], }; let specificity = selector.specificity(); - assert_eq!(specificity, Specificity::new(0, 0, 1)); + assert_eq!(specificity, vec![Specificity::new(0, 0, 1)]); let selector = CssSelector { - parts: vec![ + parts: vec![vec![ CssSelectorPart::Class("myclass".to_string()), CssSelectorPart::Class("otherclass".to_string()), - ], + ]], }; let specificity = selector.specificity(); - assert_eq!(specificity, Specificity::new(0, 2, 0)); + assert_eq!(specificity, vec![Specificity::new(0, 2, 0)]); } #[test] diff --git a/crates/gosub_styling/src/render_tree.rs b/crates/gosub_styling/src/render_tree.rs index 6680890e0..4957f21a8 100644 --- a/crates/gosub_styling/src/render_tree.rs +++ b/crates/gosub_styling/src/render_tree.rs @@ -1,14 +1,9 @@ use std::collections::HashMap; use std::fmt::{Debug, Formatter}; -use crate::property_definitions::get_css_definitions; -use crate::shorthands::FixList; -use crate::styling::{ - match_selector, prop_is_inherit, CssProperties, CssProperty, DeclarationProperty, -}; use log::warn; -use gosub_css3::stylesheet::{CssDeclaration, CssOrigin, CssSelector, CssStylesheet, CssValue}; +use gosub_css3::stylesheet::{CssDeclaration, CssOrigin, CssStylesheet, CssValue, Specificity}; use gosub_html5::node::data::element::ElementData; use gosub_html5::node::{NodeData, NodeId}; use gosub_html5::parser::document::{DocumentHandle, TreeIterator}; @@ -16,6 +11,12 @@ use gosub_render_backend::geo::Size; use gosub_render_backend::layout::{HasTextLayout, Layout, LayoutTree, Layouter, Node, TextLayout}; use gosub_shared::types::Result; +use crate::property_definitions::get_css_definitions; +use crate::shorthands::FixList; +use crate::styling::{ + match_selector, prop_is_inherit, CssProperties, CssProperty, DeclarationProperty, +}; + mod desc; const INLINE_ELEMENTS: [&str; 31] = [ @@ -291,11 +292,13 @@ impl RenderTree { for rule in sheet.rules.iter() { for selector in rule.selectors().iter() { - if !match_selector( + let (matched, specificity) = match_selector( DocumentHandle::clone(&document), current_node_id, selector, - ) { + ); + + if !matched { continue; } @@ -328,7 +331,12 @@ impl RenderTree { important: declaration.important, }; - add_property_to_map(&mut css_map_entry, sheet, selector, &decl); + add_property_to_map( + &mut css_map_entry, + sheet, + specificity.clone(), + &decl, + ); } } } @@ -557,7 +565,7 @@ impl RenderTree { pub fn add_property_to_map( css_map_entry: &mut CssProperties, sheet: &CssStylesheet, - selector: &CssSelector, + specificity: Specificity, declaration: &CssDeclaration, ) { let property_name = declaration.property.clone(); @@ -583,7 +591,7 @@ pub fn add_property_to_map( origin: sheet.origin.clone(), important: declaration.important, location: sheet.location.clone(), - specificity: selector.specificity(), + specificity, }; if let std::collections::hash_map::Entry::Vacant(e) = diff --git a/crates/gosub_styling/src/styling.rs b/crates/gosub_styling/src/styling.rs index 45a871961..631165256 100644 --- a/crates/gosub_styling/src/styling.rs +++ b/crates/gosub_styling/src/styling.rs @@ -14,8 +14,14 @@ pub(crate) fn match_selector( document: DocumentHandle, node_id: NodeId, selector: &CssSelector, -) -> bool { - match_selector_parts(document, node_id, &selector.parts) +) -> (bool, Specificity) { + for part in &selector.parts { + if match_selector_parts(DocumentHandle::clone(&document), node_id, part) { + return (true, Specificity::from(part.as_slice())); + } + } + + (false, Specificity::new(0, 0, 0)) } fn consume<'a, T>(this: &mut &'a [T]) -> Option<&'a T> {