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