Skip to main content

css_parse/syntax/
component_values.rs

1use super::prelude::*;
2use crate::AssociatedWhitespaceRules;
3
4use super::ComponentValue;
5
6/// <https://drafts.csswg.org/css-syntax-3/#consume-list-of-components>
7#[node]
8#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
9#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
10pub struct ComponentValues<'a> {
11	pub values: Vec<'a, ComponentValue<'a>>,
12}
13
14impl<'a> Peek<'a> for ComponentValues<'a> {
15	const PEEK_KINDSET: KindSet = ComponentValue::PEEK_KINDSET;
16}
17
18impl<'a> Parse<'a> for ComponentValues<'a> {
19	// https://drafts.csswg.org/css-syntax-3/#consume-list-of-components
20	fn parse<Iter>(p: &mut Parser<'a, Iter>) -> Result<Self>
21	where
22		Iter: Iterator<Item = Cursor> + Clone,
23	{
24		let mut values = Vec::new_in(p.alloc());
25		let mut last_was_whitespace = false;
26		let mut trailing_whitespace = None;
27
28		loop {
29			if p.at_end() {
30				break;
31			}
32			if p.next_is_stop() {
33				break;
34			}
35			if let Some(mut value) = p.parse_if_peek::<ComponentValue>()? {
36				if let ComponentValue::Delim(d) = value
37					&& last_was_whitespace
38					&& d.associated_whitespace().contains(AssociatedWhitespaceRules::EnforceAfter)
39				{
40					let rules = d.associated_whitespace() | AssociatedWhitespaceRules::EnforceBefore;
41					value = ComponentValue::Delim(d.with_associated_whitespace(rules))
42				}
43				// Whitespace at either edge of the list separates nothing - CSS discards it when consuming a
44				// declaration value or an at-rule prelude - so it is trivia and a minifier can remove it.
45				// Whitespace between two values is grammar (`foo(.a .b)` is not `foo(.a.b)`) and stays significant.
46				last_was_whitespace = match value {
47					ComponentValue::Whitespace(ws) => {
48						if values.is_empty() {
49							value = ComponentValue::Whitespace(ws.with_significant_whitespace(false));
50						} else if trailing_whitespace.is_none() {
51							trailing_whitespace = Some(values.len());
52						}
53						true
54					}
55					_ => {
56						trailing_whitespace = None;
57						false
58					}
59				};
60				values.push(value);
61			} else {
62				break;
63			}
64		}
65
66		if let Some(index) = trailing_whitespace {
67			for value in &mut values[index..] {
68				if let ComponentValue::Whitespace(ws) = value {
69					*value = ComponentValue::Whitespace(ws.with_significant_whitespace(false));
70				}
71			}
72		}
73		Ok(Self { values })
74	}
75}
76
77impl<'a, M: NodeMetadata> NodeWithMetadata<M> for ComponentValues<'a> {
78	fn metadata(&self) -> M {
79		M::default()
80	}
81}
82
83impl<'a> DeclarationValue<'a, ()> for ComponentValues<'a> {
84	fn parse_custom_declaration_value<Iter>(p: &mut Parser<'a, Iter>, _name: Cursor) -> Result<Self>
85	where
86		Iter: Iterator<Item = crate::Cursor> + Clone,
87	{
88		Self::parse(p)
89	}
90
91	fn is_computed_declaration_value<Iter>(p: &Parser<'a, Iter>, c: Cursor) -> bool
92	where
93		Iter: Iterator<Item = crate::Cursor> + Clone,
94	{
95		<Self as Peek>::peek(p, c)
96	}
97
98	fn parse_computed_declaration_value<Iter>(p: &mut Parser<'a, Iter>, _name: Cursor) -> Result<Self>
99	where
100		Iter: Iterator<Item = crate::Cursor> + Clone,
101	{
102		Self::parse(p)
103	}
104
105	fn parse_unknown_declaration_value<Iter>(p: &mut Parser<'a, Iter>, _name: Cursor) -> Result<Self>
106	where
107		Iter: Iterator<Item = crate::Cursor> + Clone,
108	{
109		Self::parse(p)
110	}
111}
112
113impl<'a> ToCursors for ComponentValues<'a> {
114	fn to_cursors(&self, s: &mut impl CursorSink) {
115		ToCursors::to_cursors(&self.values, s)
116	}
117}
118
119impl<'a> ToSpan for ComponentValues<'a> {
120	fn to_span(&self) -> Span {
121		self.values.to_span()
122	}
123}
124
125// Implement for ComponentValues - compare sequences, ignoring whitespace
126impl<'a> SemanticEq for ComponentValues<'a> {
127	fn semantic_eq(&self, other: &Self) -> bool {
128		self.values.semantic_eq(&other.values)
129	}
130}
131
132#[cfg(test)]
133mod tests {
134	use super::*;
135	use crate::{EmptyAtomSet, test_helpers::*};
136
137	#[test]
138	fn test_writes() {
139		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, "body{color:black}");
140		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, "body");
141	}
142
143	#[test]
144	fn test_writes_with_trivia() {
145		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, "/*comment*/foo");
146		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, " /*comment*/ foo");
147		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, "/*a*/foo/*b*/bar");
148		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, "foo/*comment*/bar");
149		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, " \t foo");
150		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, " /*start*/ foo /*mid*/ bar");
151		assert_parse!(EmptyAtomSet::ATOMS, ComponentValues, "/*comment*/foo");
152	}
153}