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 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}