Skip to main content

css_ast/selector/
attribute.rs

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