1use css_parse::{
2 AtRule, CommaSeparated, Parse, Parser, Result as ParserResult, RuleList, T, atkeyword_set, function_set,
3};
4use csskit_derives::{Parse, Peek, ToCursors, ToSpan, Visitable};
5
6use crate::stylesheet::Rule;
7
8atkeyword_set!(pub struct AtDocumentKeyword "document");
9
10#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
13#[visit]
14pub struct DocumentRule<'a>(pub AtRule<AtDocumentKeyword, DocumentMatcherList<'a>, DocumentRuleBlock<'a>>);
15
16#[derive(Parse, Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
18pub struct DocumentMatcherList<'a>(pub CommaSeparated<'a, DocumentMatcher>);
19
20function_set!(
21 pub enum DocumentMatcherFunctionKeyword {
22 Url: "url",
23 UrlPrefix: "url-prefix",
24 Domain: "domain",
25 MediaDocument: "media-document",
26 Regexp: "regexp",
27 }
28);
29
30#[derive(Peek, ToCursors, ToSpan, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
31#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
32#[visit(self)]
33pub enum DocumentMatcher {
34 Url(T![Url]),
35 UrlFunction(T![Function], T![String], T![')']),
36 UrlPrefix(T![Function], T![String], T![')']),
37 Domain(T![Function], T![String], T![')']),
38 MediaDocument(T![Function], T![String], T![')']),
39 Regexp(T![Function], T![String], T![')']),
40}
41
42impl<'a> Parse<'a> for DocumentMatcher {
43 fn parse(p: &mut Parser<'a>) -> ParserResult<Self> {
44 if p.peek::<T![Url]>() {
45 Ok(Self::Url(p.parse::<T![Url]>()?))
46 } else {
47 match p.parse::<DocumentMatcherFunctionKeyword>()? {
48 DocumentMatcherFunctionKeyword::Url(function) => {
49 let string = p.parse::<T![String]>()?;
50 let close = p.parse::<T![')']>()?;
51 Ok(Self::UrlFunction(function, string, close))
52 }
53 DocumentMatcherFunctionKeyword::UrlPrefix(function) => {
54 let string = p.parse::<T![String]>()?;
55 let close = p.parse::<T![')']>()?;
56 Ok(Self::UrlPrefix(function, string, close))
57 }
58 DocumentMatcherFunctionKeyword::Domain(function) => {
59 let string = p.parse::<T![String]>()?;
60 let close = p.parse::<T![')']>()?;
61 Ok(Self::UrlPrefix(function, string, close))
62 }
63 DocumentMatcherFunctionKeyword::MediaDocument(function) => {
64 let string = p.parse::<T![String]>()?;
65 let close = p.parse::<T![')']>()?;
66 Ok(Self::UrlPrefix(function, string, close))
67 }
68 DocumentMatcherFunctionKeyword::Regexp(function) => {
69 let string = p.parse::<T![String]>()?;
70 let close = p.parse::<T![')']>()?;
71 Ok(Self::UrlPrefix(function, string, close))
72 }
73 }
74 }
75 }
76}
77
78#[derive(Parse, Peek, ToSpan, ToCursors, Visitable, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
80pub struct DocumentRuleBlock<'a>(RuleList<'a, Rule<'a>>);
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85 use css_parse::assert_parse;
86
87 #[test]
88 fn size_test() {
89 assert_eq!(std::mem::size_of::<DocumentRule>(), 128);
90 assert_eq!(std::mem::size_of::<DocumentMatcher>(), 40);
91 assert_eq!(std::mem::size_of::<DocumentRuleBlock>(), 64);
92 }
93
94 #[test]
95 fn test_writes() {
96 assert_parse!(DocumentRule, r#"@document url("http://www.w3.org"){}"#);
97 assert_parse!(DocumentRule, r#"@document domain("mozilla.org"){}"#);
98 assert_parse!(DocumentRule, r#"@document url-prefix("http://www.w3.org/Style/"){}"#);
99 assert_parse!(DocumentRule, r#"@document media-document("video"){}"#);
100 assert_parse!(DocumentRule, r#"@document regexp("https:.*"){}"#);
101 assert_parse!(
102 DocumentRule,
103 r#"@document url(http://www.w3.org),url-prefix("http://www.w3.org/Style/"),domain("mozilla.org"){}"#
104 );
105 assert_parse!(
106 DocumentRule,
107 r#"@document url(http://www.w3.org),url-prefix("http://www.w3.org/Style/"),domain("mozilla.org"){body{color:black}}"#
108 );
109 }
110}