1use crate::{CssAtomSet, CssMetadata, RuleKind, StyleValue, rules, stylerule::StyleRule};
2use bumpalo::collections::Vec;
3use css_parse::{
4 ComponentValues, Cursor, Diagnostic, NodeWithMetadata, Parse, Parser, QualifiedRule, Result as ParserResult,
5 RuleVariants, StyleSheet as StyleSheetTrait, T,
6};
7use csskit_derives::{Parse, Peek, SemanticEq, ToCursors, ToSpan};
8
9#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
12#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
13pub struct StyleSheet<'a> {
14 pub rules: Vec<'a, Rule<'a>>,
15 #[to_cursors(skip)]
16 #[cfg_attr(feature = "serde", serde(skip))]
17 #[cfg_attr(feature = "visitable", visit(skip))]
18 meta: CssMetadata,
19}
20
21impl<'a> NodeWithMetadata<CssMetadata> for StyleSheet<'a> {
22 fn metadata(&self) -> CssMetadata {
23 self.meta
24 }
25}
26
27impl<'a> Parse<'a> for StyleSheet<'a> {
32 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
33 where
34 I: Iterator<Item = Cursor> + Clone,
35 {
36 let (rules, meta) = Self::parse_stylesheet(p)?;
37 Ok(Self { rules, meta })
38 }
39}
40
41impl<'a> StyleSheetTrait<'a, CssMetadata> for StyleSheet<'a> {
42 type Rule = Rule<'a>;
43}
44
45macro_rules! apply_rules {
46 ($macro: ident) => {
47 $macro! {
48 Charset(CharsetRule): CssAtomSet::Charset,
49 ColorProfile(ColorProfileRule): CssAtomSet::ColorProfile,
50 Container(ContainerRule<'a>): CssAtomSet::Container,
51 CounterStyle(CounterStyleRule): CssAtomSet::CounterStyle,
52 FontFace(FontFaceRule<'a>): CssAtomSet::FontFace,
53 FontFeatureValues(FontFeatureValuesRule): CssAtomSet::FontFeatureValues,
54 FontPaletteValues(FontPaletteValuesRule): CssAtomSet::FontPaletteValues,
55 Import(ImportRule): CssAtomSet::Import,
56 Keyframes(KeyframesRule<'a>): CssAtomSet::Keyframes,
57 Layer(LayerRule<'a>): CssAtomSet::Layer,
58 Media(MediaRule<'a>): CssAtomSet::Media,
59 Namespace(NamespaceRule): CssAtomSet::Namespace,
60 Page(PageRule<'a>): CssAtomSet::Page,
61 Property(PropertyRule<'a>): CssAtomSet::Property,
62 Scope(ScopeRule): CssAtomSet::Scope,
63 StartingStyle(StartingStyleRule): CssAtomSet::StartingStyle,
64 Supports(SupportsRule<'a>): CssAtomSet::Supports,
65
66 Document(DocumentRule<'a>): CssAtomSet::Document,
68
69 WebkitKeyframes(WebkitKeyframesRule<'a>): CssAtomSet::_WebkitKeyframes,
71
72 MozDocument(MozDocumentRule<'a>): CssAtomSet::_MozDocument,
74 }
75 };
76}
77
78#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
79#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
80#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
81pub struct UnknownAtRule<'a> {
82 name: T![AtKeyword],
83 prelude: ComponentValues<'a>,
84 block: ComponentValues<'a>,
85}
86
87impl<'a> NodeWithMetadata<CssMetadata> for UnknownAtRule<'a> {
88 fn metadata(&self) -> CssMetadata {
89 CssMetadata { rule_kinds: RuleKind::Unknown, ..Default::default() }
90 }
91}
92
93#[derive(Parse, Peek, ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
94#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
95#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(self))]
96pub struct UnknownQualifiedRule<'a>(
97 QualifiedRule<'a, ComponentValues<'a>, StyleValue<'a>, ComponentValues<'a>, CssMetadata>,
98);
99
100impl<'a> NodeWithMetadata<CssMetadata> for UnknownQualifiedRule<'a> {
101 fn metadata(&self) -> CssMetadata {
102 let mut meta = self.0.metadata();
103 meta.rule_kinds |= RuleKind::Unknown;
104 meta
105 }
106}
107
108macro_rules! rule {
109 ( $(
110 $name: ident($ty: ident$(<$a: lifetime>)?): $str: pat,
111 )+ ) => {
112 #[allow(clippy::large_enum_variant)] #[derive(ToSpan, ToCursors, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
115 #[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable))]
116 #[cfg_attr(feature = "serde", derive(serde::Serialize), serde(untagged))]
117 pub enum Rule<'a> {
118 $(
119 $name(rules::$ty$(<$a>)?),
120 )+
121 UnknownAt(UnknownAtRule<'a>),
122 Style(StyleRule<'a>),
123 Unknown(UnknownQualifiedRule<'a>)
124 }
125 }
126}
127
128apply_rules!(rule);
129
130impl<'a> NodeWithMetadata<CssMetadata> for Rule<'a> {
131 fn metadata(&self) -> CssMetadata {
132 macro_rules! match_rule {
133 ( $(
134 $name: ident($ty: ident$(<$a: lifetime>)?): $atoms: pat,
135 )+ ) => {
136 match self {
137 $(Self::$name(r) => r.metadata(),)+
138 Self::Style(r) => r.metadata(),
139 Self::UnknownAt(r) => r.metadata(),
140 Self::Unknown(r) => r.metadata(),
141 }
142 }
143 }
144 apply_rules!(match_rule)
145 }
146}
147
148impl<'a> RuleVariants<'a> for Rule<'a> {
149 fn parse_at_rule<I>(p: &mut Parser<'a, I>, c: Cursor) -> ParserResult<Self>
150 where
151 I: Iterator<Item = Cursor> + Clone,
152 {
153 macro_rules! parse_rule {
154 ( $(
155 $name: ident($ty: ident$(<$a: lifetime>)?): $atoms: pat,
156 )+ ) => {
157 match p.to_atom::<CssAtomSet>(c) {
158 $($atoms => p.parse::<rules::$ty>().map(Self::$name),)+
159 _ => Err(Diagnostic::new(p.next(), Diagnostic::unexpected))?,
160 }
161 }
162 }
163 apply_rules!(parse_rule)
164 }
165
166 fn parse_unknown_at_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
167 where
168 I: Iterator<Item = Cursor> + Clone,
169 {
170 p.parse::<UnknownAtRule>().map(Self::UnknownAt)
171 }
172
173 fn parse_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
174 where
175 I: Iterator<Item = Cursor> + Clone,
176 {
177 p.parse::<StyleRule>().map(Self::Style)
178 }
179
180 fn parse_unknown_qualified_rule<I>(p: &mut Parser<'a, I>, _name: Cursor) -> ParserResult<Self>
181 where
182 I: Iterator<Item = Cursor> + Clone,
183 {
184 p.parse::<UnknownQualifiedRule>().map(Self::Unknown)
185 }
186}
187
188impl<'a> Parse<'a> for Rule<'a> {
189 fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
190 where
191 I: Iterator<Item = Cursor> + Clone,
192 {
193 Self::parse_rule_variants(p)
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200 use crate::CssAtomSet;
201 use css_parse::assert_parse;
202
203 #[test]
204 fn size_test() {
205 assert_eq!(std::mem::size_of::<StyleSheet>(), 56);
206 assert_eq!(std::mem::size_of::<Rule>(), 520);
207 }
208
209 #[test]
210 fn test_writes() {
211 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{}");
212 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{color:red;}");
213 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body,tr:nth-child(n-1){}");
214 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{width:1px;}");
215 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "body{width:1px;}.a{width:2px;}");
216 assert_parse!(CssAtomSet::ATOMS, StyleSheet, "one:1;a{two:2}");
217 assert_parse!(CssAtomSet::ATOMS, Rule, "@media screen{}", Rule::Media(_));
218 assert_parse!(CssAtomSet::ATOMS, Rule, "@layer foo{}", Rule::Layer(_));
219 }
220}