Skip to main content

css_ast/selector/
attribute.rs

1use crate::CssAtomSet;
2use css_parse::{Cursor, KindSet, Parse, Parser, Result as ParserResult, T};
3use csskit_derives::*;
4
5use super::NamespacePrefix;
6
7#[derive(Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
9#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
10#[derive(csskit_derives::NodeWithMetadata)]
11pub struct Attribute {
12	#[cfg_attr(feature = "visitable", visit(skip))]
13	pub open: T!['['],
14	#[cfg_attr(feature = "visitable", visit(skip))]
15	pub namespace_prefix: Option<NamespacePrefix>,
16	#[cfg_attr(feature = "visitable", visit(skip))]
17	pub attribute: T![Ident],
18	pub operator: Option<AttributeOperator>,
19	pub value: Option<AttributeValue>,
20	pub modifier: Option<AttributeModifier>,
21	#[cfg_attr(feature = "visitable", visit(skip))]
22	pub close: Option<T![']']>,
23}
24
25impl<'a> Parse<'a> for Attribute {
26	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
27	where
28		I: Iterator<Item = Cursor> + Clone,
29	{
30		let open = p.parse::<T!['[']>()?;
31		let mut namespace_prefix = if p.peek::<T![*|]>() { Some(p.parse::<NamespacePrefix>()?) } else { None };
32		let mut attribute = p.parse::<T![Ident]>()?;
33		let skip = p.set_skip(KindSet::NONE);
34		// namespace_prefix might be `<Ident> '|' <Ident>`
35		if namespace_prefix.is_none() && p.peek::<T![|]>() && !p.peek::<T![|=]>() {
36			let pipe = p.parse::<T![|]>();
37			let ident = p.parse::<T![Ident]>();
38			p.set_skip(skip);
39			namespace_prefix = Some(NamespacePrefix::Name(attribute, pipe?));
40			attribute = ident?;
41		}
42		p.set_skip(skip);
43		let operator = p.parse_if_peek::<AttributeOperator>()?;
44		let value = if operator.is_some() { Some(p.parse::<AttributeValue>()?) } else { None };
45		let modifier =
46			if value.is_some() && p.peek::<AttributeModifier>() { Some(p.parse::<AttributeModifier>()?) } else { None };
47		let close = p.parse_if_peek::<T![']']>()?;
48		Ok(Self { open, namespace_prefix, attribute, operator, value, modifier, close })
49	}
50}
51
52#[derive(Parse, ToSpan, Peek, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
53#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
54#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
55#[derive(csskit_derives::NodeWithMetadata)]
56pub enum AttributeOperator {
57	Exact(T![=]),
58	SpaceList(T![~=]),
59	LangPrefix(T![|=]),
60	Prefix(T![^=]),
61	Suffix(T!["$="]),
62	Contains(T![*=]),
63}
64
65#[derive(
66	Peek, Parse, ToCursors, IntoCursor, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
67)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
69#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
70#[derive(csskit_derives::NodeWithMetadata)]
71pub enum AttributeValue {
72	String(T![String]),
73	Ident(T![Ident]),
74}
75
76#[derive(
77	Parse, Peek, ToCursors, IntoCursor, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
78)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
80#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
81#[derive(csskit_derives::NodeWithMetadata)]
82#[cfg_attr(
83	feature = "css_feature_data",
84	derive(::csskit_derives::ToCSSFeature),
85	css_feature("css.selectors.attribute")
86)]
87pub enum AttributeModifier {
88	#[cfg_attr(feature = "css_feature_data", css_feature("css.selectors.attribute.case_sensitive_modifier"))]
89	#[atom(CssAtomSet::S)]
90	Sensitive(T![Ident]),
91	#[cfg_attr(feature = "css_feature_data", css_feature("css.selectors.attribute.case_insensitive_modifier"))]
92	#[atom(CssAtomSet::I)]
93	Insensitive(T![Ident]),
94}
95
96#[cfg(test)]
97mod tests {
98	use super::*;
99	use crate::CssAtomSet;
100	use css_parse::assert_parse;
101
102	#[test]
103	fn size_test() {
104		assert_eq!(std::mem::size_of::<Attribute>(), 128);
105		assert_eq!(std::mem::size_of::<AttributeOperator>(), 28);
106		assert_eq!(std::mem::size_of::<AttributeModifier>(), 16);
107		assert_eq!(std::mem::size_of::<AttributeValue>(), 16);
108	}
109
110	#[test]
111	fn test_writes() {
112		assert_parse!(CssAtomSet::ATOMS, Attribute, "[foo]");
113		assert_parse!(CssAtomSet::ATOMS, Attribute, "[foo='bar']");
114		assert_parse!(CssAtomSet::ATOMS, Attribute, "[foo=\"bar\"]");
115		assert_parse!(CssAtomSet::ATOMS, Attribute, "[foo='bar']");
116		assert_parse!(CssAtomSet::ATOMS, Attribute, "[attr*='foo']");
117		assert_parse!(CssAtomSet::ATOMS, Attribute, "[attr='foo']");
118		assert_parse!(CssAtomSet::ATOMS, Attribute, "[*|attr='foo']");
119		assert_parse!(CssAtomSet::ATOMS, Attribute, "[x|attr='foo']");
120		assert_parse!(CssAtomSet::ATOMS, Attribute, "[attr|='foo']");
121		assert_parse!(CssAtomSet::ATOMS, Attribute, "[attr|=foo i]");
122		assert_parse!(CssAtomSet::ATOMS, Attribute, "[attr|=foo s]");
123		assert_parse!(CssAtomSet::ATOMS, Attribute, "[attr|='foo'i]");
124		assert_parse!(CssAtomSet::ATOMS, Attribute, "[attr|='foo's]");
125	}
126
127	#[cfg(feature = "css_feature_data")]
128	#[test]
129	fn test_feature_data() {
130		use crate::assert_feature_id;
131		assert_feature_id!("i", AttributeModifier, "css.selectors.attribute.case_insensitive_modifier");
132		assert_feature_id!("s", AttributeModifier, "css.selectors.attribute.case_sensitive_modifier");
133	}
134}