parcel_css/
declaration.rs

1//! CSS declarations.
2
3use std::borrow::Cow;
4use std::ops::Range;
5
6use crate::context::PropertyHandlerContext;
7use crate::error::{ParserError, PrinterError};
8use crate::parser::ParserOptions;
9use crate::printer::Printer;
10use crate::properties::box_shadow::BoxShadowHandler;
11use crate::properties::masking::MaskHandler;
12use crate::properties::{
13  align::AlignHandler,
14  animation::AnimationHandler,
15  background::BackgroundHandler,
16  border::BorderHandler,
17  contain::ContainerHandler,
18  display::DisplayHandler,
19  flex::FlexHandler,
20  font::FontHandler,
21  grid::GridHandler,
22  list::ListStyleHandler,
23  margin_padding::*,
24  outline::OutlineHandler,
25  overflow::OverflowHandler,
26  position::PositionHandler,
27  prefix_handler::{FallbackHandler, PrefixHandler},
28  size::SizeHandler,
29  text::TextDecorationHandler,
30  transform::TransformHandler,
31  transition::TransitionHandler,
32};
33use crate::properties::{Property, PropertyId};
34use crate::targets::Browsers;
35use crate::traits::{PropertyHandler, ToCss};
36use crate::values::string::CowArcStr;
37use cssparser::*;
38
39/// A CSS declaration block.
40///
41/// Properties are separated into a list of `!important` declararations,
42/// and a list of normal declarations. This reduces memory usage compared
43/// with storing a boolean along with each property.
44#[derive(Debug, PartialEq, Clone)]
45#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
46pub struct DeclarationBlock<'i> {
47  /// A list of `!important` declarations in the block.
48  #[cfg_attr(feature = "serde", serde(borrow))]
49  pub important_declarations: Vec<Property<'i>>,
50  /// A list of normal declarations in the block.
51  pub declarations: Vec<Property<'i>>,
52}
53
54impl<'i> DeclarationBlock<'i> {
55  /// Parses a declaration block from CSS syntax.
56  pub fn parse<'a, 'o, 't>(
57    input: &mut Parser<'i, 't>,
58    options: &'a ParserOptions<'o, 'i>,
59  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
60    let mut important_declarations = DeclarationList::new();
61    let mut declarations = DeclarationList::new();
62    let mut parser = DeclarationListParser::new(
63      input,
64      PropertyDeclarationParser {
65        important_declarations: &mut important_declarations,
66        declarations: &mut declarations,
67        options,
68      },
69    );
70    while let Some(res) = parser.next() {
71      if let Err((err, _)) = res {
72        if options.error_recovery {
73          options.warn(err);
74          continue;
75        }
76        return Err(err);
77      }
78    }
79
80    Ok(DeclarationBlock {
81      important_declarations,
82      declarations,
83    })
84  }
85
86  /// Parses a declaration block from a string.
87  pub fn parse_string<'o>(
88    input: &'i str,
89    options: ParserOptions<'o, 'i>,
90  ) -> Result<Self, ParseError<'i, ParserError<'i>>> {
91    let mut input = ParserInput::new(input);
92    let mut parser = Parser::new(&mut input);
93    let result = Self::parse(&mut parser, &options)?;
94    parser.expect_exhausted()?;
95    Ok(result)
96  }
97}
98
99impl<'i> ToCss for DeclarationBlock<'i> {
100  fn to_css<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
101  where
102    W: std::fmt::Write,
103  {
104    let len = self.declarations.len() + self.important_declarations.len();
105    let mut i = 0;
106
107    macro_rules! write {
108      ($decls: expr, $important: literal) => {
109        for decl in &$decls {
110          decl.to_css(dest, $important)?;
111          if i != len - 1 {
112            dest.write_char(';')?;
113            dest.whitespace()?;
114          }
115          i += 1;
116        }
117      };
118    }
119
120    write!(self.declarations, false);
121    write!(self.important_declarations, true);
122    Ok(())
123  }
124}
125
126impl<'i> DeclarationBlock<'i> {
127  pub(crate) fn to_css_block<W>(&self, dest: &mut Printer<W>) -> Result<(), PrinterError>
128  where
129    W: std::fmt::Write,
130  {
131    dest.whitespace()?;
132    dest.write_char('{')?;
133    dest.indent();
134
135    let mut i = 0;
136    let len = self.declarations.len() + self.important_declarations.len();
137
138    macro_rules! write {
139      ($decls: expr, $important: literal) => {
140        for decl in &$decls {
141          dest.newline()?;
142          decl.to_css(dest, $important)?;
143          if i != len - 1 || !dest.minify {
144            dest.write_char(';')?;
145          }
146          i += 1;
147        }
148      };
149    }
150
151    write!(self.declarations, false);
152    write!(self.important_declarations, true);
153
154    dest.dedent();
155    dest.newline()?;
156    dest.write_char('}')
157  }
158}
159
160impl<'i> DeclarationBlock<'i> {
161  pub(crate) fn minify(
162    &mut self,
163    handler: &mut DeclarationHandler<'i>,
164    important_handler: &mut DeclarationHandler<'i>,
165    context: &mut PropertyHandlerContext<'i, '_>,
166  ) {
167    macro_rules! handle {
168      ($decls: expr, $handler: expr, $important: literal) => {
169        for decl in $decls.iter() {
170          context.is_important = $important;
171          let handled = $handler.handle_property(decl, context);
172
173          if !handled {
174            $handler.decls.push(decl.clone());
175          }
176        }
177      };
178    }
179
180    handle!(self.important_declarations, important_handler, true);
181    handle!(self.declarations, handler, false);
182
183    handler.finalize(context);
184    important_handler.finalize(context);
185    self.important_declarations = std::mem::take(&mut important_handler.decls);
186    self.declarations = std::mem::take(&mut handler.decls);
187  }
188
189  /// Returns whether the declaration block is empty.
190  pub fn is_empty(&self) -> bool {
191    return self.declarations.is_empty() && self.important_declarations.is_empty();
192  }
193
194  pub(crate) fn property_location<'t>(
195    &self,
196    input: &mut Parser<'i, 't>,
197    index: usize,
198  ) -> Result<(Range<SourceLocation>, Range<SourceLocation>), ParseError<'i, ParserError<'i>>> {
199    // Skip to the requested property index.
200    for _ in 0..index {
201      input.expect_ident()?;
202      input.expect_colon()?;
203      input.parse_until_after(Delimiter::Semicolon, |parser| {
204        while parser.next().is_ok() {}
205        Ok(())
206      })?;
207    }
208
209    // Get property name range.
210    input.skip_whitespace();
211    let key_start = input.current_source_location();
212    input.expect_ident()?;
213    let key_end = input.current_source_location();
214    let key_range = key_start..key_end;
215
216    input.expect_colon()?;
217    input.skip_whitespace();
218
219    // Get value range.
220    let val_start = input.current_source_location();
221    input.parse_until_before(Delimiter::Semicolon, |parser| {
222      while parser.next().is_ok() {}
223      Ok(())
224    })?;
225    let val_end = input.current_source_location();
226    let val_range = val_start..val_end;
227
228    Ok((key_range, val_range))
229  }
230}
231
232impl<'i> DeclarationBlock<'i> {
233  /// Returns an iterator over all properties in the declaration.
234  pub fn iter(&self) -> impl std::iter::DoubleEndedIterator<Item = (&Property<'i>, bool)> {
235    self
236      .declarations
237      .iter()
238      .map(|property| (property, false))
239      .chain(self.important_declarations.iter().map(|property| (property, true)))
240  }
241
242  /// Returns a mutable iterator over all properties in the declaration.
243  pub fn iter_mut(&mut self) -> impl std::iter::DoubleEndedIterator<Item = &mut Property<'i>> {
244    self.declarations.iter_mut().chain(self.important_declarations.iter_mut())
245  }
246
247  /// Returns the value for a given property id based on the properties in this declaration block.
248  ///
249  /// If the property is a shorthand, the result will be a combined value of all of the included
250  /// longhands, or `None` if some of the longhands are not declared. Otherwise, the value will be
251  /// either an explicitly declared longhand, or a value extracted from a shorthand property.
252  pub fn get<'a>(&'a self, property_id: &PropertyId) -> Option<(Cow<'a, Property<'i>>, bool)> {
253    if property_id.is_shorthand() {
254      if let Some((shorthand, important)) = property_id.shorthand_value(&self) {
255        return Some((Cow::Owned(shorthand), important));
256      }
257    } else {
258      for (property, important) in self.iter().rev() {
259        if property.property_id() == *property_id {
260          return Some((Cow::Borrowed(property), important));
261        }
262
263        if let Some(val) = property.longhand(&property_id) {
264          return Some((Cow::Owned(val), important));
265        }
266      }
267    }
268
269    None
270  }
271
272  /// Sets the value and importance for a given property, replacing any existing declarations.
273  ///
274  /// If the property already exists within the declaration block, it is updated in place. Otherwise,
275  /// a new declaration is appended. When updating a longhand property and a shorthand is defined which
276  /// includes the longhand, the shorthand will be updated rather than appending a new declaration.
277  pub fn set(&mut self, property: Property<'i>, important: bool) {
278    let property_id = property.property_id();
279    let declarations = if important {
280      // Remove any non-important properties with this id.
281      self.declarations.retain(|decl| decl.property_id() != property_id);
282      &mut self.important_declarations
283    } else {
284      // Remove any important properties with this id.
285      self.important_declarations.retain(|decl| decl.property_id() != property_id);
286      &mut self.declarations
287    };
288
289    let longhands = property_id.longhands().unwrap_or_else(|| vec![property.property_id()]);
290
291    for decl in declarations.iter_mut().rev() {
292      {
293        // If any of the longhands being set are in the same logical property group as any of the
294        // longhands in this property, but in a different category (i.e. logical or physical),
295        // then we cannot modify in place, and need to append a new property.
296        let id = decl.property_id();
297        let id_longhands = id.longhands().unwrap_or_else(|| vec![id]);
298        if longhands.iter().any(|longhand| {
299          let logical_group = longhand.logical_group();
300          let category = longhand.category();
301
302          logical_group.is_some()
303            && id_longhands.iter().any(|id_longhand| {
304              logical_group == id_longhand.logical_group() && category != id_longhand.category()
305            })
306        }) {
307          break;
308        }
309      }
310
311      if decl.property_id() == property_id {
312        *decl = property;
313        return;
314      }
315
316      // Update shorthand.
317      if decl.set_longhand(&property).is_ok() {
318        return;
319      }
320    }
321
322    declarations.push(property)
323  }
324
325  /// Removes all declarations of the given property id from the declaration block.
326  ///
327  /// When removing a longhand property and a shorthand is defined which includes the longhand,
328  /// the shorthand will be split apart into its component longhand properties, minus the property
329  /// to remove. When removing a shorthand, all included longhand properties are also removed.
330  pub fn remove(&mut self, property_id: &PropertyId) {
331    fn remove<'i, 'a>(declarations: &mut Vec<Property<'i>>, property_id: &PropertyId<'a>) {
332      let longhands = property_id.longhands().unwrap_or(vec![]);
333      let mut i = 0;
334      while i < declarations.len() {
335        let replacement = {
336          let property = &declarations[i];
337          let id = property.property_id();
338          if id == *property_id || longhands.contains(&id) {
339            // If the property matches the requested property id, or is a longhand
340            // property that is included in the requested shorthand, remove it.
341            None
342          } else if longhands.is_empty() && id.longhands().unwrap_or(vec![]).contains(&property_id) {
343            // If this is a shorthand property that includes the requested longhand,
344            // split it apart into its component longhands, excluding the requested one.
345            Some(
346              id.longhands()
347                .unwrap()
348                .iter()
349                .filter_map(|longhand| {
350                  if *longhand == *property_id {
351                    None
352                  } else {
353                    property.longhand(longhand)
354                  }
355                })
356                .collect::<Vec<Property>>(),
357            )
358          } else {
359            i += 1;
360            continue;
361          }
362        };
363
364        match replacement {
365          Some(properties) => {
366            let count = properties.len();
367            declarations.splice(i..i + 1, properties);
368            i += count;
369          }
370          None => {
371            declarations.remove(i);
372          }
373        }
374      }
375    }
376
377    remove(&mut self.declarations, property_id);
378    remove(&mut self.important_declarations, property_id);
379  }
380}
381
382struct PropertyDeclarationParser<'a, 'o, 'i> {
383  important_declarations: &'a mut Vec<Property<'i>>,
384  declarations: &'a mut Vec<Property<'i>>,
385  options: &'a ParserOptions<'o, 'i>,
386}
387
388/// Parse a declaration within {} block: `color: blue`
389impl<'a, 'o, 'i> cssparser::DeclarationParser<'i> for PropertyDeclarationParser<'a, 'o, 'i> {
390  type Declaration = ();
391  type Error = ParserError<'i>;
392
393  fn parse_value<'t>(
394    &mut self,
395    name: CowRcStr<'i>,
396    input: &mut cssparser::Parser<'i, 't>,
397  ) -> Result<Self::Declaration, cssparser::ParseError<'i, Self::Error>> {
398    parse_declaration(
399      name,
400      input,
401      &mut self.declarations,
402      &mut self.important_declarations,
403      &self.options,
404    )
405  }
406}
407
408/// Default methods reject all at rules.
409impl<'a, 'o, 'i> AtRuleParser<'i> for PropertyDeclarationParser<'a, 'o, 'i> {
410  type Prelude = ();
411  type AtRule = ();
412  type Error = ParserError<'i>;
413}
414
415pub(crate) fn parse_declaration<'i, 't>(
416  name: CowRcStr<'i>,
417  input: &mut cssparser::Parser<'i, 't>,
418  declarations: &mut DeclarationList<'i>,
419  important_declarations: &mut DeclarationList<'i>,
420  options: &ParserOptions,
421) -> Result<(), cssparser::ParseError<'i, ParserError<'i>>> {
422  let property = input.parse_until_before(Delimiter::Bang, |input| {
423    Property::parse(PropertyId::from(CowArcStr::from(name)), input, options)
424  })?;
425  let important = input
426    .try_parse(|input| {
427      input.expect_delim('!')?;
428      input.expect_ident_matching("important")
429    })
430    .is_ok();
431  if important {
432    important_declarations.push(property);
433  } else {
434    declarations.push(property);
435  }
436  Ok(())
437}
438
439pub(crate) type DeclarationList<'i> = Vec<Property<'i>>;
440
441pub(crate) struct DeclarationHandler<'i> {
442  background: BackgroundHandler<'i>,
443  border: BorderHandler<'i>,
444  outline: OutlineHandler,
445  flex: FlexHandler,
446  grid: GridHandler<'i>,
447  align: AlignHandler,
448  size: SizeHandler,
449  margin: MarginHandler<'i>,
450  padding: PaddingHandler<'i>,
451  scroll_margin: ScrollMarginHandler<'i>,
452  scroll_padding: ScrollPaddingHandler<'i>,
453  font: FontHandler<'i>,
454  text: TextDecorationHandler<'i>,
455  list: ListStyleHandler<'i>,
456  transition: TransitionHandler<'i>,
457  animation: AnimationHandler<'i>,
458  display: DisplayHandler<'i>,
459  position: PositionHandler,
460  inset: InsetHandler<'i>,
461  overflow: OverflowHandler,
462  transform: TransformHandler,
463  box_shadow: BoxShadowHandler,
464  mask: MaskHandler<'i>,
465  container: ContainerHandler<'i>,
466  fallback: FallbackHandler,
467  prefix: PrefixHandler,
468  decls: DeclarationList<'i>,
469}
470
471impl<'i> DeclarationHandler<'i> {
472  pub fn new(targets: Option<Browsers>) -> Self {
473    DeclarationHandler {
474      background: BackgroundHandler::new(targets),
475      border: BorderHandler::new(targets),
476      outline: OutlineHandler::new(targets),
477      flex: FlexHandler::new(targets),
478      grid: GridHandler::default(),
479      align: AlignHandler::new(targets),
480      size: SizeHandler::default(),
481      margin: MarginHandler::default(),
482      padding: PaddingHandler::default(),
483      scroll_margin: ScrollMarginHandler::default(),
484      scroll_padding: ScrollPaddingHandler::default(),
485      font: FontHandler::default(),
486      text: TextDecorationHandler::new(targets),
487      list: ListStyleHandler::new(targets),
488      transition: TransitionHandler::new(targets),
489      animation: AnimationHandler::new(targets),
490      display: DisplayHandler::new(targets),
491      position: PositionHandler::new(targets),
492      inset: InsetHandler::default(),
493      overflow: OverflowHandler::new(targets),
494      transform: TransformHandler::new(targets),
495      box_shadow: BoxShadowHandler::new(targets),
496      mask: MaskHandler::default(),
497      container: ContainerHandler::default(),
498      fallback: FallbackHandler::new(targets),
499      prefix: PrefixHandler::new(targets),
500      decls: DeclarationList::new(),
501    }
502  }
503
504  pub fn handle_property(
505    &mut self,
506    property: &Property<'i>,
507    context: &mut PropertyHandlerContext<'i, '_>,
508  ) -> bool {
509    if !context.unused_symbols.is_empty()
510      && matches!(property, Property::Custom(custom) if context.unused_symbols.contains(custom.name.as_ref()))
511    {
512      return true;
513    }
514
515    self.background.handle_property(property, &mut self.decls, context)
516      || self.border.handle_property(property, &mut self.decls, context)
517      || self.outline.handle_property(property, &mut self.decls, context)
518      || self.flex.handle_property(property, &mut self.decls, context)
519      || self.grid.handle_property(property, &mut self.decls, context)
520      || self.align.handle_property(property, &mut self.decls, context)
521      || self.size.handle_property(property, &mut self.decls, context)
522      || self.margin.handle_property(property, &mut self.decls, context)
523      || self.padding.handle_property(property, &mut self.decls, context)
524      || self.scroll_margin.handle_property(property, &mut self.decls, context)
525      || self.scroll_padding.handle_property(property, &mut self.decls, context)
526      || self.font.handle_property(property, &mut self.decls, context)
527      || self.text.handle_property(property, &mut self.decls, context)
528      || self.list.handle_property(property, &mut self.decls, context)
529      || self.transition.handle_property(property, &mut self.decls, context)
530      || self.animation.handle_property(property, &mut self.decls, context)
531      || self.display.handle_property(property, &mut self.decls, context)
532      || self.position.handle_property(property, &mut self.decls, context)
533      || self.inset.handle_property(property, &mut self.decls, context)
534      || self.overflow.handle_property(property, &mut self.decls, context)
535      || self.transform.handle_property(property, &mut self.decls, context)
536      || self.box_shadow.handle_property(property, &mut self.decls, context)
537      || self.mask.handle_property(property, &mut self.decls, context)
538      || self.container.handle_property(property, &mut self.decls, context)
539      || self.fallback.handle_property(property, &mut self.decls, context)
540      || self.prefix.handle_property(property, &mut self.decls, context)
541  }
542
543  pub fn finalize(&mut self, context: &mut PropertyHandlerContext<'i, '_>) {
544    self.background.finalize(&mut self.decls, context);
545    self.border.finalize(&mut self.decls, context);
546    self.outline.finalize(&mut self.decls, context);
547    self.flex.finalize(&mut self.decls, context);
548    self.grid.finalize(&mut self.decls, context);
549    self.align.finalize(&mut self.decls, context);
550    self.size.finalize(&mut self.decls, context);
551    self.margin.finalize(&mut self.decls, context);
552    self.padding.finalize(&mut self.decls, context);
553    self.scroll_margin.finalize(&mut self.decls, context);
554    self.scroll_padding.finalize(&mut self.decls, context);
555    self.font.finalize(&mut self.decls, context);
556    self.text.finalize(&mut self.decls, context);
557    self.list.finalize(&mut self.decls, context);
558    self.transition.finalize(&mut self.decls, context);
559    self.animation.finalize(&mut self.decls, context);
560    self.display.finalize(&mut self.decls, context);
561    self.position.finalize(&mut self.decls, context);
562    self.inset.finalize(&mut self.decls, context);
563    self.overflow.finalize(&mut self.decls, context);
564    self.transform.finalize(&mut self.decls, context);
565    self.box_shadow.finalize(&mut self.decls, context);
566    self.mask.finalize(&mut self.decls, context);
567    self.container.finalize(&mut self.decls, context);
568    self.fallback.finalize(&mut self.decls, context);
569    self.prefix.finalize(&mut self.decls, context);
570  }
571}