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 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(Peek, Parse, ToCursors, IntoCursor, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
66#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
67#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
68#[derive(csskit_derives::NodeWithMetadata)]
69pub enum AttributeValue {
70 String(T![String]),
71 Ident(T![Ident]),
72}
73
74#[derive(Parse, Peek, ToCursors, IntoCursor, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
75#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
76#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
77#[derive(csskit_derives::NodeWithMetadata)]
78#[cfg_attr(
79 feature = "css_feature_data",
80 derive(::csskit_derives::ToCSSFeature),
81 css_feature("css.selectors.attribute")
82)]
83pub enum AttributeModifier {
84 #[cfg_attr(feature = "css_feature_data", css_feature("css.selectors.attribute.case_sensitive_modifier"))]
85 #[atom(CssAtomSet::S)]
86 Sensitive(T![Ident]),
87 #[cfg_attr(feature = "css_feature_data", css_feature("css.selectors.attribute.case_insensitive_modifier"))]
88 #[atom(CssAtomSet::I)]
89 Insensitive(T![Ident]),
90}
91
92#[cfg(test)]
93mod tests {
94 use super::*;
95 use crate::CssAtomSet;
96 use css_parse::assert_parse;
97
98 #[test]
99 fn size_test() {
100 assert_eq!(std::mem::size_of::<Attribute>(), 128);
101 assert_eq!(std::mem::size_of::<AttributeOperator>(), 28);
102 assert_eq!(std::mem::size_of::<AttributeModifier>(), 16);
103 assert_eq!(std::mem::size_of::<AttributeValue>(), 16);
104 }
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}