Skip to main content

css_ast/selector/
attribute.rs

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