Skip to content

Commit

Permalink
Merge pull request #588 from Sharktheone/css/descendant-combinator
Browse files Browse the repository at this point in the history
Reimplment selector matching, fix descendant combinator & implement next & subsequent child combinator
  • Loading branch information
Sharktheone authored Sep 9, 2024
2 parents cdb1316 + 2823d18 commit c6921d1
Show file tree
Hide file tree
Showing 4 changed files with 413 additions and 289 deletions.
81 changes: 34 additions & 47 deletions crates/gosub_css3/src/convert/ast_converter.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::node::{Node as CssNode, NodeType};
use crate::stylesheet::{
CssDeclaration, CssOrigin, CssRule, CssSelector, CssSelectorPart, CssSelectorType,
CssStylesheet, CssValue, MatcherType,
AttributeSelector, Combinator, CssDeclaration, CssOrigin, CssRule, CssSelector,
CssSelectorPart, CssStylesheet, CssValue, MatcherType,
};
use anyhow::anyhow;
use gosub_shared::types::Result;
Expand Down Expand Up @@ -98,57 +98,44 @@ pub fn convert_ast_to_stylesheet(

for node in node.as_selector() {
let part = match &*node.node_type {
NodeType::Ident { value } => CssSelectorPart {
type_: CssSelectorType::Type,
value: value.clone(),
..Default::default()
},
NodeType::ClassSelector { value } => CssSelectorPart {
type_: CssSelectorType::Class,
value: value.clone(),
..Default::default()
},
NodeType::Combinator { value } => CssSelectorPart {
type_: CssSelectorType::Combinator,
value: value.clone(),
..Default::default()
},
NodeType::IdSelector { value } => CssSelectorPart {
type_: CssSelectorType::Id,
value: value.clone(),
..Default::default()
},
NodeType::TypeSelector { value, .. } if value == "*" => CssSelectorPart {
type_: CssSelectorType::Universal,
value: "*".to_string(),
..Default::default()
},
NodeType::PseudoClassSelector { value, .. } => CssSelectorPart {
type_: CssSelectorType::PseudoClass,
value: value.to_string(),
..Default::default()
},
NodeType::PseudoElementSelector { value, .. } => CssSelectorPart {
type_: CssSelectorType::PseudoElement,
value: value.clone(),
..Default::default()
},
NodeType::TypeSelector { value, .. } => CssSelectorPart {
type_: CssSelectorType::Type,
value: value.clone(),
..Default::default()
},
NodeType::Ident { value } => CssSelectorPart::Type(value.clone()),
NodeType::ClassSelector { value } => CssSelectorPart::Class(value.clone()),
NodeType::Combinator { value } => {
let combinator = match value.as_str() {
">" => Combinator::Child,
"+" => Combinator::NextSibling,
"~" => Combinator::SubsequentSibling,
" " => Combinator::Descendant,
"||" => Combinator::Column,
"|" => Combinator::Namespace,
_ => return Err(anyhow!("Unknown combinator: {}", value)),
};

CssSelectorPart::Combinator(combinator)
}
NodeType::IdSelector { value } => CssSelectorPart::Id(value.clone()),
NodeType::TypeSelector { value, .. } if value == "*" => {
CssSelectorPart::Universal
}
NodeType::PseudoClassSelector { value, .. } => {
CssSelectorPart::PseudoClass(value.to_string())
}
NodeType::PseudoElementSelector { value, .. } => {
CssSelectorPart::PseudoElement(value.to_string())
}
NodeType::TypeSelector { value, .. } => {
CssSelectorPart::Type(value.clone())
}
NodeType::AttributeSelector {
name, value, flags, ..
} => CssSelectorPart {
type_: CssSelectorType::Attribute,
} => CssSelectorPart::Attribute(Box::new(AttributeSelector {
name: name.clone(),
matcher: MatcherType::Equals, // @todo: this needs to be parsed
value: value.clone(),
flags: flags.clone(),
},
case_insensitive: flags.eq_ignore_ascii_case("i"),
})),
_ => {
panic!("Unknown selector type: {:?}", node);
return Err(anyhow!("Unsupported selector part: {:?}", node.node_type))
}
};
selector.parts.push(part);
Expand Down
153 changes: 72 additions & 81 deletions crates/gosub_css3/src/stylesheet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,14 @@ impl CssSelector {
let mut class_count = 0;
let mut element_count = 0;
for part in &self.parts {
match part.type_ {
CssSelectorType::Id => {
match part {
CssSelectorPart::Id(_) => {
id_count += 1;
}
CssSelectorType::Class => {
CssSelectorPart::Class(_) => {
class_count += 1;
}
CssSelectorType::Type => {
CssSelectorPart::Type(_) => {
element_count += 1;
}
_ => {}
Expand All @@ -88,47 +88,81 @@ impl CssSelector {
}
}

/// @todo: it would be nicer to have a struct for each type of selector part, but for now we'll keep it simple
/// Represents a CSS selector part, which has a type and value (e.g. type=Class, class="my-class")
#[derive(PartialEq, Clone, Default)]
pub struct CssSelectorPart {
pub type_: CssSelectorType,
pub value: String,
pub matcher: MatcherType,
pub enum CssSelectorPart {
#[default]
Universal,
Attribute(Box<AttributeSelector>),
Class(String),
Id(String),
PseudoClass(String),
PseudoElement(String),
Combinator(Combinator),
Type(String),
}

#[derive(PartialEq, Clone, Default)]
pub struct AttributeSelector {
pub name: String,
pub flags: String,
pub matcher: MatcherType,
pub value: String,
pub case_insensitive: bool,
}

#[derive(Debug, PartialEq, Clone)]
pub enum Combinator {
Descendant,
Child,
NextSibling,
SubsequentSibling,
Column,
Namespace,
}

impl Display for Combinator {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Combinator::Descendant => write!(f, " "),
Combinator::Child => write!(f, ">"),
Combinator::NextSibling => write!(f, "+"),
Combinator::SubsequentSibling => write!(f, "~"),
Combinator::Column => write!(f, "||"),
Combinator::Namespace => write!(f, "|"),
}
}
}

impl Debug for CssSelectorPart {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.type_ {
CssSelectorType::Universal => {
match self {
CssSelectorPart::Universal => {
write!(f, "*")
}
CssSelectorType::Attribute => {
CssSelectorPart::Attribute(selector) => {
write!(
f,
"[{} {} {} {}]",
self.name, self.matcher, self.value, self.flags
selector.name, selector.matcher, selector.value, selector.case_insensitive
)
}
CssSelectorType::Class => {
write!(f, ".{}", self.value)
CssSelectorPart::Class(name) => {
write!(f, ".{}", name)
}
CssSelectorType::Id => {
write!(f, "#{}", self.value)
CssSelectorPart::Id(name) => {
write!(f, "#{}", name)
}
CssSelectorType::PseudoClass => {
write!(f, ":{}", self.value)
CssSelectorPart::PseudoClass(name) => {
write!(f, ":{}", name)
}
CssSelectorType::PseudoElement => {
write!(f, "::{}", self.value)
CssSelectorPart::PseudoElement(name) => {
write!(f, "::{}", name)
}
CssSelectorType::Combinator => {
write!(f, "'{}'", self.value)
CssSelectorPart::Combinator(combinator) => {
write!(f, "'{}'", combinator)
}
CssSelectorType::Type => {
write!(f, "{}", self.value)
CssSelectorPart::Type(name) => {
write!(f, "{}", name)
}
}
}
Expand Down Expand Up @@ -414,11 +448,7 @@ mod test {
fn test_css_rule() {
let rule = CssRule {
selectors: vec![CssSelector {
parts: vec![CssSelectorPart {
type_: CssSelectorType::Type,
value: "h1".to_string(),
..Default::default()
}],
parts: vec![CssSelectorPart::Type("h1".to_string())],
}],
declarations: vec![CssDeclaration {
property: "color".to_string(),
Expand All @@ -428,16 +458,9 @@ mod test {
};

assert_eq!(rule.selectors().len(), 1);
assert_eq!(
rule.selectors()
.first()
.unwrap()
.parts
.first()
.unwrap()
.value,
"h1"
);
let part = rule.selectors().first().unwrap().parts.first().unwrap();

assert_eq!(part, &CssSelectorPart::Type("h1".to_string()));
assert_eq!(rule.declarations().len(), 1);
assert_eq!(rule.declarations().first().unwrap().property, "color");
}
Expand All @@ -446,21 +469,9 @@ mod test {
fn test_specificity() {
let selector = CssSelector {
parts: vec![
CssSelectorPart {
type_: CssSelectorType::Type,
value: "h1".to_string(),
..Default::default()
},
CssSelectorPart {
type_: CssSelectorType::Class,
value: "myclass".to_string(),
..Default::default()
},
CssSelectorPart {
type_: CssSelectorType::Id,
value: "myid".to_string(),
..Default::default()
},
CssSelectorPart::Type("h1".to_string()),
CssSelectorPart::Class("myclass".to_string()),
CssSelectorPart::Id("myid".to_string()),
],
};

Expand All @@ -469,45 +480,25 @@ mod test {

let selector = CssSelector {
parts: vec![
CssSelectorPart {
type_: CssSelectorType::Type,
value: "h1".to_string(),
..Default::default()
},
CssSelectorPart {
type_: CssSelectorType::Class,
value: "myclass".to_string(),
..Default::default()
},
CssSelectorPart::Type("h1".to_string()),
CssSelectorPart::Class("myclass".to_string()),
],
};

let specificity = selector.specificity();
assert_eq!(specificity, Specificity::new(0, 1, 1));

let selector = CssSelector {
parts: vec![CssSelectorPart {
type_: CssSelectorType::Type,
value: "h1".to_string(),
..Default::default()
}],
parts: vec![CssSelectorPart::Type("h1".to_string())],
};

let specificity = selector.specificity();
assert_eq!(specificity, Specificity::new(0, 0, 1));

let selector = CssSelector {
parts: vec![
CssSelectorPart {
type_: CssSelectorType::Class,
value: "myclass".to_string(),
..Default::default()
},
CssSelectorPart {
type_: CssSelectorType::Class,
value: "otherclass".to_string(),
..Default::default()
},
CssSelectorPart::Class("myclass".to_string()),
CssSelectorPart::Class("otherclass".to_string()),
],
};

Expand Down
Loading

0 comments on commit c6921d1

Please sign in to comment.