css_ast/selector/
namespace.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::Tag;
5
6// https://drafts.csswg.org/selectors/#combinators
7#[derive(ToSpan, ToCursors, Visitable, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
8#[cfg_attr(feature = "serde", derive(serde::Serialize), serde(rename_all = "kebab-case"))]
9#[visit(self)]
10pub struct Namespace {
11	pub prefix: Option<NamespacePrefix>,
12	pub tag: NamespaceTag,
13}
14
15impl<'a> Parse<'a> for Namespace {
16	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
17		if p.peek::<T![*|]>() {
18			let prefix = p.parse::<NamespacePrefix>()?;
19			let tag = p.parse::<NamespaceTag>()?;
20			return Ok(Self { prefix: Some(prefix), tag });
21		}
22		if p.peek::<T![|]>() {
23			let prefix = p.parse::<NamespacePrefix>()?;
24			let tag = p.parse::<NamespaceTag>()?;
25			return Ok(Self { prefix: Some(prefix), tag });
26		}
27
28		let ident = p.parse::<T![Ident]>()?;
29		let skip = p.set_skip(KindSet::NONE);
30		if p.peek::<T![|]>() && !p.peek::<T![|=]>() {
31			let pipe = p.parse::<T![|]>();
32			let tag = p.parse::<NamespaceTag>();
33			p.set_skip(skip);
34			let prefix = NamespacePrefix::Name(ident, pipe?);
35			return Ok(Self { prefix: Some(prefix), tag: tag? });
36		}
37		let tag = p.parse::<NamespaceTag>()?;
38		Ok(Self { prefix: None, tag })
39	}
40}
41
42#[derive(ToSpan, ToCursors, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
44pub enum NamespacePrefix {
45	None(T![|]),
46	Name(T![Ident], T![|]),
47	Wildcard(T![*], T![|]),
48}
49
50impl<'a> Parse<'a> for NamespacePrefix {
51	fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
52		if p.peek::<T![|]>() {
53			let pipe = p.parse::<T![|]>()?;
54			Ok(Self::None(pipe))
55		} else if p.peek::<T![*]>() {
56			let star = p.parse::<T![*]>()?;
57			let skip = p.set_skip(KindSet::NONE);
58			let pipe = p.parse::<T![|]>();
59			p.set_skip(skip);
60			let pipe = pipe?;
61			Ok(Self::Wildcard(star, pipe))
62		} else {
63			let star = p.parse::<T![Ident]>()?;
64			let skip = p.set_skip(KindSet::NONE);
65			let pipe = p.parse::<T![|]>();
66			p.set_skip(skip);
67			let pipe = pipe?;
68			Ok(Self::Name(star, pipe))
69		}
70	}
71}
72
73#[derive(Peek, ToCursors, IntoCursor, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
75pub enum NamespaceTag {
76	Wildcard(T![*]),
77	Tag(Tag),
78}
79
80impl<'a> Build<'a> for NamespaceTag {
81	fn build(p: &Parser<'a>, c: Cursor) -> Self {
82		if <T![*]>::peek(p, c) { Self::Wildcard(<T![*]>::build(p, c)) } else { Self::Tag(Tag::build(p, c)) }
83	}
84}
85
86#[cfg(test)]
87mod tests {
88	use super::*;
89	use css_parse::{assert_parse, assert_parse_error};
90
91	#[test]
92	fn size_test() {
93		assert_eq!(std::mem::size_of::<Namespace>(), 48);
94	}
95
96	#[test]
97	fn test_writes() {
98		assert_parse!(Namespace, "*|a");
99		assert_parse!(Namespace, "html|div");
100		assert_parse!(Namespace, "|span");
101	}
102
103	#[test]
104	fn test_errors() {
105		assert_parse_error!(Namespace, "* | a");
106	}
107}