cssparser/rules_and_declarations.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5// https://drafts.csswg.org/css-syntax/#parsing
6
7use super::{BasicParseError, BasicParseErrorKind, Delimiter, ParseError, Parser, Token};
8use crate::cow_rc_str::CowRcStr;
9use crate::parser::{ParseUntilErrorBehavior, ParserState, parse_nested_block, parse_until_after};
10use crate::tokenizer::SourceLocation;
11
12/// Parse `!important`.
13///
14/// Typical usage is `input.try_parse(parse_important).is_ok()`
15/// at the end of a `DeclarationParser::parse_value` implementation.
16pub fn parse_important(input: &mut Parser) -> Result<(), BasicParseError> {
17 input.expect_delim('!')?;
18 input.expect_ident_matching("important")
19}
20
21/// A trait to provide various parsing of declaration values.
22///
23/// For example, there could be different implementations for property declarations in style rules
24/// and for descriptors in `@font-face` rules.
25pub trait DeclarationParser<'i> {
26 /// The finished representation of a declaration.
27 type Declaration;
28
29 /// The error type that is included in the ParseError value that can be returned.
30 type Error;
31
32 /// Parse the value of a declaration with the given `name`.
33 ///
34 /// Return the finished representation for the declaration
35 /// as returned by `RuleBodyParser::next`,
36 /// or an `Err(..)` to ignore the entire declaration as invalid.
37 ///
38 /// Declaration name matching should be case-insensitive in the ASCII range.
39 /// This can be done with `std::ascii::Ascii::eq_ignore_ascii_case`,
40 /// or with the `match_ignore_ascii_case!` macro.
41 ///
42 /// The given `input` is a "delimited" parser
43 /// that ends wherever the declaration value should end.
44 /// (In declaration lists, before the next semicolon or end of the current block.)
45 ///
46 /// If `!important` can be used in a given context,
47 /// `input.try_parse(parse_important).is_ok()` should be used at the end
48 /// of the implementation of this method and the result should be part of the return value.
49 fn parse_value(
50 &mut self,
51 _name: CowRcStr<'i>,
52 _input: &mut Parser<'i>,
53 _declaration_start: &ParserState,
54 ) -> Result<Self::Declaration, ParseError<Self::Error>> {
55 Err(ParseError::unexpected_token())
56 }
57}
58
59/// A trait to provide various parsing of at-rules.
60///
61/// For example, there could be different implementations for top-level at-rules
62/// (`@media`, `@font-face`, …)
63/// and for page-margin rules inside `@page`.
64///
65/// Default implementations that reject all at-rules are provided,
66/// so that `impl AtRuleParser<(), ()> for ... {}` can be used
67/// for using `RuleBodyParser` to parse a declarations list with only qualified rules.
68pub trait AtRuleParser<'i> {
69 /// The intermediate representation of prelude of an at-rule.
70 type Prelude;
71
72 /// The finished representation of an at-rule.
73 type AtRule;
74
75 /// The error type that is included in the ParseError value that can be returned.
76 type Error;
77
78 /// Parse the prelude of an at-rule with the given `name`.
79 ///
80 /// Return the representation of the prelude and the type of at-rule,
81 /// or an `Err(..)` to ignore the entire at-rule as invalid.
82 ///
83 /// The prelude is the part after the at-keyword
84 /// and before the `;` semicolon or `{ /* ... */ }` block.
85 ///
86 /// At-rule name matching should be case-insensitive in the ASCII range.
87 /// This can be done with `std::ascii::Ascii::eq_ignore_ascii_case`,
88 /// or with the `match_ignore_ascii_case!` macro.
89 ///
90 /// The given `input` is a "delimited" parser
91 /// that ends wherever the prelude should end.
92 /// (Before the next semicolon, the next `{`, or the end of the current block.)
93 fn parse_prelude(
94 &mut self,
95 _name: CowRcStr<'i>,
96 _input: &mut Parser<'i>,
97 ) -> Result<Self::Prelude, ParseError<Self::Error>> {
98 Err(ParseError::from_basic_kind(
99 BasicParseErrorKind::AtRuleInvalid,
100 ))
101 }
102
103 /// End an at-rule which doesn't have block. Return the finished
104 /// representation of the at-rule.
105 ///
106 /// The state passed in is the parser state at the start of the prelude.
107 ///
108 /// This is only called when `parse_prelude` returned `WithoutBlock`, and
109 /// either the `;` semicolon indeed follows the prelude, or parser is at
110 /// the end of the input.
111 #[allow(clippy::result_unit_err)]
112 fn rule_without_block(
113 &mut self,
114 prelude: Self::Prelude,
115 start: &ParserState,
116 ) -> Result<Self::AtRule, ()> {
117 let _ = prelude;
118 let _ = start;
119 Err(())
120 }
121
122 /// Parse the content of a `{ /* ... */ }` block for the body of the at-rule.
123 ///
124 /// The state passed in is the parser state at the start of the prelude.
125 ///
126 /// Return the finished representation of the at-rule
127 /// as returned by `StyleSheetParser::next` or `RuleBodyParser::next`,
128 /// or an `Err(..)` to ignore the entire at-rule as invalid.
129 ///
130 /// This is only called when `parse_prelude` returned `WithBlock`, and a block
131 /// was indeed found following the prelude.
132 fn parse_block(
133 &mut self,
134 prelude: Self::Prelude,
135 start: &ParserState,
136 _input: &mut Parser<'i>,
137 ) -> Result<Self::AtRule, ParseError<Self::Error>> {
138 let _ = prelude;
139 let _ = start;
140 Err(ParseError::from_basic_kind(
141 BasicParseErrorKind::AtRuleBodyInvalid,
142 ))
143 }
144}
145
146/// A trait to provide various parsing of qualified rules.
147///
148/// For example, there could be different implementations for top-level qualified rules (i.e. style
149/// rules with Selectors as prelude) and for qualified rules inside `@keyframes` (keyframe rules
150/// with keyframe selectors as prelude).
151///
152/// Default implementations that reject all qualified rules are provided, so that
153/// `impl QualifiedRuleParser<(), ()> for ... {}` can be used for example for using
154/// `StyleSheetParser` to parse a rule list with only at-rules (such as inside
155/// `@font-feature-values`).
156pub trait QualifiedRuleParser<'i> {
157 /// The intermediate representation of a qualified rule prelude.
158 type Prelude;
159
160 /// The finished representation of a qualified rule.
161 type QualifiedRule;
162
163 /// The error type that is included in the ParseError value that can be returned.
164 type Error;
165
166 /// Parse the prelude of a qualified rule. For style rules, this is as Selector list.
167 ///
168 /// Return the representation of the prelude,
169 /// or an `Err(..)` to ignore the entire at-rule as invalid.
170 ///
171 /// The prelude is the part before the `{ /* ... */ }` block.
172 ///
173 /// The given `input` is a "delimited" parser
174 /// that ends where the prelude should end (before the next `{`).
175 fn parse_prelude(
176 &mut self,
177 _input: &mut Parser<'i>,
178 ) -> Result<Self::Prelude, ParseError<Self::Error>> {
179 Err(ParseError::from_basic_kind(
180 BasicParseErrorKind::QualifiedRuleInvalid,
181 ))
182 }
183
184 /// Parse the content of a `{ /* ... */ }` block for the body of the qualified rule.
185 ///
186 /// The state passed in is the parser state at the start of the prelude.
187 ///
188 /// Return the finished representation of the qualified rule
189 /// as returned by `StyleSheetParser::next`,
190 /// or an `Err(..)` to ignore the entire at-rule as invalid.
191 fn parse_block(
192 &mut self,
193 prelude: Self::Prelude,
194 start: &ParserState,
195 _input: &mut Parser<'i>,
196 ) -> Result<Self::QualifiedRule, ParseError<Self::Error>> {
197 let _ = prelude;
198 let _ = start;
199 Err(ParseError::from_basic_kind(
200 BasicParseErrorKind::QualifiedRuleInvalid,
201 ))
202 }
203}
204
205/// Provides an iterator for rule bodies and declaration lists.
206pub struct RuleBodyParser<'i, 'a, P, I, E> {
207 /// The input given to the parser.
208 pub input: &'a mut Parser<'i>,
209 /// The parser given to `RuleBodyParser::new`
210 pub parser: &'a mut P,
211
212 _phantom: std::marker::PhantomData<(I, E)>,
213}
214
215/// A parser for a rule body item.
216pub trait RuleBodyItemParser<'i, DeclOrRule, Error>:
217 DeclarationParser<'i, Declaration = DeclOrRule, Error = Error>
218 + QualifiedRuleParser<'i, QualifiedRule = DeclOrRule, Error = Error>
219 + AtRuleParser<'i, AtRule = DeclOrRule, Error = Error>
220{
221 /// Whether we should attempt to parse declarations. If you know you won't, returning false
222 /// here is slightly faster.
223 fn parse_declarations(&self) -> bool;
224 /// Whether we should attempt to parse qualified rules. If you know you won't, returning false
225 /// would be slightly faster.
226 fn parse_qualified(&self) -> bool;
227}
228
229impl<'i, 'a, P, I, E> RuleBodyParser<'i, 'a, P, I, E> {
230 /// Create a new `RuleBodyParser` for the given `input` and `parser`.
231 ///
232 /// Note that all CSS declaration lists can on principle contain at-rules.
233 /// Even if no such valid at-rule exists (yet),
234 /// this affects error handling: at-rules end at `{}` blocks, not just semicolons.
235 ///
236 /// The given `parser` therefore needs to implement
237 /// both `DeclarationParser` and `AtRuleParser` traits.
238 /// However, the latter can be an empty `impl`
239 /// since `AtRuleParser` provides default implementations of its methods.
240 ///
241 /// The return type for finished declarations and at-rules also needs to be the same,
242 /// since `<RuleBodyParser as Iterator>::next` can return either.
243 /// It could be a custom enum.
244 pub fn new(input: &'a mut Parser<'i>, parser: &'a mut P) -> Self {
245 Self {
246 input,
247 parser,
248 _phantom: std::marker::PhantomData,
249 }
250 }
251}
252
253/// https://drafts.csswg.org/css-syntax/#consume-a-blocks-contents
254impl<'i, I, P, E> Iterator for RuleBodyParser<'i, '_, P, I, E>
255where
256 P: RuleBodyItemParser<'i, I, E>,
257{
258 type Item = Result<I, (ParseError<E>, &'i str, SourceLocation)>;
259
260 fn next(&mut self) -> Option<Self::Item> {
261 loop {
262 self.input.skip_whitespace();
263 let start = self.input.state();
264 match self.input.next_including_whitespace_and_comments().ok()? {
265 Token::CloseCurlyBracket
266 | Token::WhiteSpace(..)
267 | Token::Semicolon
268 | Token::Comment(..) => continue,
269 Token::AtKeyword(name) => {
270 let name = name.clone();
271 return Some(parse_at_rule(&start, name, self.input, &mut *self.parser));
272 }
273 // https://drafts.csswg.org/css-syntax/#consume-a-declaration bails out just to
274 // keep parsing as a qualified rule if the token is not an ident, so we implement
275 // that in a slightly more straight-forward way
276 Token::Ident(name) if self.parser.parse_declarations() => {
277 let name = name.clone();
278 let parse_qualified = self.parser.parse_qualified();
279 let result = {
280 let error_behavior = if parse_qualified {
281 ParseUntilErrorBehavior::Stop
282 } else {
283 ParseUntilErrorBehavior::Consume
284 };
285 let parser = &mut self.parser;
286 parse_until_after(
287 self.input,
288 Delimiter::Semicolon,
289 error_behavior,
290 |input| {
291 input.expect_colon()?;
292 parser.parse_value(name, input, &start)
293 },
294 )
295 };
296 if result.is_err() && parse_qualified {
297 self.input.reset(&start);
298 // We ignore the resulting error here. The property declaration parse error
299 // is likely to be more relevant.
300 if let Ok(qual) = parse_qualified_rule(
301 &start,
302 self.input,
303 &mut *self.parser,
304 /* nested = */ true,
305 ) {
306 return Some(Ok(qual));
307 }
308 }
309
310 return Some(result.map_err(|e| {
311 (
312 e,
313 self.input.slice_from(start.position()),
314 start.source_location(),
315 )
316 }));
317 }
318 _ => {
319 let result = if self.parser.parse_qualified() {
320 self.input.reset(&start);
321 let nested = self.parser.parse_declarations();
322 parse_qualified_rule(&start, self.input, &mut *self.parser, nested)
323 } else {
324 self.input.parse_until_after(Delimiter::Semicolon, |_| {
325 Err(ParseError::unexpected_token())
326 })
327 };
328 return Some(result.map_err(|e| {
329 (
330 e,
331 self.input.slice_from(start.position()),
332 start.source_location(),
333 )
334 }));
335 }
336 }
337 }
338 }
339}
340
341/// Provides an iterator for rule list parsing at the top-level of a stylesheet.
342pub struct StyleSheetParser<'i, 'a, P> {
343 /// The input given.
344 pub input: &'a mut Parser<'i>,
345
346 /// The parser given.
347 pub parser: &'a mut P,
348
349 any_rule_so_far: bool,
350}
351
352impl<'i, 'a, R, P, E> StyleSheetParser<'i, 'a, P>
353where
354 P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E>
355 + AtRuleParser<'i, AtRule = R, Error = E>,
356{
357 /// The given `parser` needs to implement both `QualifiedRuleParser` and `AtRuleParser` traits.
358 /// However, either of them can be an empty `impl` since the traits provide default
359 /// implementations of their methods.
360 ///
361 /// The return type for finished qualified rules and at-rules also needs to be the same,
362 /// since `<StyleSheetParser as Iterator>::next` can return either. It could be a custom enum.
363 pub fn new(input: &'a mut Parser<'i>, parser: &'a mut P) -> Self {
364 Self {
365 input,
366 parser,
367 any_rule_so_far: false,
368 }
369 }
370}
371
372/// `StyleSheetParser` is an iterator that yields `Ok(_)` for a rule or an `Err(..)` for an invalid one.
373impl<'i, R, P, E> Iterator for StyleSheetParser<'i, '_, P>
374where
375 P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E>
376 + AtRuleParser<'i, AtRule = R, Error = E>,
377{
378 type Item = Result<R, (ParseError<E>, &'i str, SourceLocation)>;
379
380 fn next(&mut self) -> Option<Self::Item> {
381 loop {
382 self.input.skip_cdc_and_cdo();
383 let start = self.input.state();
384 let at_keyword = match self.input.next_byte()? {
385 b'@' => match self.input.next_including_whitespace_and_comments() {
386 Ok(Token::AtKeyword(name)) => Some(name.clone()),
387 _ => {
388 self.input.reset(&start);
389 None
390 }
391 },
392 _ => None,
393 };
394
395 if let Some(name) = at_keyword {
396 let first_stylesheet_rule = !self.any_rule_so_far;
397 self.any_rule_so_far = true;
398 if first_stylesheet_rule && name.eq_ignore_ascii_case("charset") {
399 let delimiters = Delimiter::Semicolon | Delimiter::CurlyBracketBlock;
400 let _: Result<(), ParseError<()>> =
401 self.input.parse_until_after(delimiters, |_| Ok(()));
402 } else {
403 return Some(parse_at_rule(
404 &start,
405 name.clone(),
406 self.input,
407 &mut *self.parser,
408 ));
409 }
410 } else {
411 self.any_rule_so_far = true;
412 let result = parse_qualified_rule(
413 &start,
414 self.input,
415 &mut *self.parser,
416 /* nested = */ false,
417 );
418 return Some(result.map_err(|e| {
419 (
420 e,
421 self.input.slice_from(start.position()),
422 start.source_location(),
423 )
424 }));
425 }
426 }
427 }
428}
429
430/// Parse a single declaration, such as an `( /* ... */ )` parenthesis in an `@supports` prelude.
431pub fn parse_one_declaration<'i, P, E>(
432 input: &mut Parser<'i>,
433 parser: &mut P,
434) -> Result<<P as DeclarationParser<'i>>::Declaration, (ParseError<E>, &'i str, SourceLocation)>
435where
436 P: DeclarationParser<'i, Error = E>,
437{
438 let start = input.state();
439 let start_position = input.position();
440 input
441 .parse_entirely(|input| {
442 let name = input.expect_ident()?.clone();
443 input.expect_colon()?;
444 parser.parse_value(name, input, &start)
445 })
446 .map_err(|e| (e, input.slice_from(start_position), start.source_location()))
447}
448
449/// Parse a single rule, such as for CSSOM’s `CSSStyleSheet.insertRule`.
450pub fn parse_one_rule<'i, R, P, E>(
451 input: &mut Parser<'i>,
452 parser: &mut P,
453) -> Result<R, ParseError<E>>
454where
455 P: QualifiedRuleParser<'i, QualifiedRule = R, Error = E>
456 + AtRuleParser<'i, AtRule = R, Error = E>,
457{
458 input.parse_entirely(|input| {
459 input.skip_whitespace();
460 let start = input.state();
461 let at_keyword = if input.next_byte() == Some(b'@') {
462 match *input.next_including_whitespace_and_comments()? {
463 Token::AtKeyword(ref name) => Some(name.clone()),
464 _ => {
465 input.reset(&start);
466 None
467 }
468 }
469 } else {
470 None
471 };
472
473 if let Some(name) = at_keyword {
474 parse_at_rule(&start, name, input, parser).map_err(|e| e.0)
475 } else {
476 parse_qualified_rule(&start, input, parser, /* nested = */ false)
477 }
478 })
479}
480
481fn parse_at_rule<'i, P, E>(
482 start: &ParserState,
483 name: CowRcStr<'i>,
484 input: &mut Parser<'i>,
485 parser: &mut P,
486) -> Result<<P as AtRuleParser<'i>>::AtRule, (ParseError<E>, &'i str, SourceLocation)>
487where
488 P: AtRuleParser<'i, Error = E>,
489{
490 let delimiters = Delimiter::Semicolon | Delimiter::CurlyBracketBlock;
491 let result = input.parse_until_before(delimiters, |input| parser.parse_prelude(name, input));
492 match result {
493 Ok(prelude) => {
494 let result = match input.next() {
495 Ok(&Token::Semicolon) | Err(_) => parser
496 .rule_without_block(prelude, start)
497 .map_err(|()| ParseError::unexpected_token()),
498 Ok(&Token::CurlyBracketBlock) => {
499 parse_nested_block(input, |input| parser.parse_block(prelude, start, input))
500 }
501 Ok(_) => unreachable!(),
502 };
503 result.map_err(|e| {
504 (
505 e,
506 input.slice_from(start.position()),
507 start.source_location(),
508 )
509 })
510 }
511 Err(error) => {
512 let end_position = input.position();
513 match input.next() {
514 Ok(&Token::CurlyBracketBlock) | Ok(&Token::Semicolon) | Err(_) => {}
515 _ => unreachable!(),
516 };
517 Err((
518 error,
519 input.slice(start.position()..end_position),
520 start.source_location(),
521 ))
522 }
523 }
524}
525
526// If the first two non-<whitespace-token> values of rule’s prelude are an <ident-token> whose
527// value starts with "--" followed by a <colon-token>, then...
528fn looks_like_a_custom_property(input: &mut Parser) -> bool {
529 let ident = match input.expect_ident() {
530 Ok(i) => i,
531 Err(..) => return false,
532 };
533 ident.starts_with("--") && input.expect_colon().is_ok()
534}
535
536// https://drafts.csswg.org/css-syntax/#consume-a-qualified-rule
537fn parse_qualified_rule<'i, P, E>(
538 start: &ParserState,
539 input: &mut Parser<'i>,
540 parser: &mut P,
541 nested: bool,
542) -> Result<<P as QualifiedRuleParser<'i>>::QualifiedRule, ParseError<E>>
543where
544 P: QualifiedRuleParser<'i, Error = E>,
545{
546 input.skip_whitespace();
547 let prelude = {
548 let state = input.state();
549 if looks_like_a_custom_property(input) {
550 // If nested is true, consume the remnants of a bad declaration from input, with
551 // nested set to true, and return nothing.
552 // If nested is false, consume a block from input, and return nothing.
553 let delimiters = if nested {
554 Delimiter::Semicolon
555 } else {
556 Delimiter::CurlyBracketBlock
557 };
558 let _: Result<(), ParseError<()>> = input.parse_until_after(delimiters, |_| Ok(()));
559 return Err(ParseError::from_basic_kind(
560 BasicParseErrorKind::QualifiedRuleInvalid,
561 ));
562 }
563 let delimiters = if nested {
564 Delimiter::Semicolon | Delimiter::CurlyBracketBlock
565 } else {
566 Delimiter::CurlyBracketBlock
567 };
568 input.reset(&state);
569 input.parse_until_before(delimiters, |input| parser.parse_prelude(input))
570 };
571
572 input.expect_curly_bracket_block()?;
573 // Do this here so that we consume the `{` even if the prelude is `Err`.
574 let prelude = prelude?;
575 parse_nested_block(input, |input| parser.parse_block(prelude, start, input))
576}