css_ast/selector/
attribute.rs

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