lewp_css/domain/properties/
specified_value.rs1use {
5 crate::{domain::Atom, parsers::ParserContext, CustomParseError},
6 cssparser::{Delimiter, ParseError, Parser, ToCss, TokenSerializationType},
7 std::{borrow::Cow, collections::HashSet, fmt},
8};
9
10#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
14pub struct SpecifiedValue {
15 pub originalCss: String,
16 }
21
22impl ToCss for SpecifiedValue {
23 fn to_css<W: fmt::Write>(&self, dest: &mut W) -> fmt::Result {
24 dest.write_str(&self.originalCss)
25 }
26}
27
28impl SpecifiedValue {
29 pub(crate) fn parse<'i, 't>(
31 _context: &ParserContext,
32 input: &mut Parser<'i, 't>,
33 ) -> Result<Self, ParseError<'i, CustomParseError<'i>>> {
34 let mut references = Some(HashSet::new());
35 let (_first, css, _last) =
36 Self::parse_self_contained_declaration_value(
37 input,
38 &mut references,
39 )?;
40 Ok(SpecifiedValue {
41 originalCss: css.into_owned(),
42 })
46 }
47
48 fn parse_self_contained_declaration_value<'i, 't>(
49 input: &mut Parser<'i, 't>,
50 references: &mut Option<HashSet<Atom>>,
51 ) -> Result<
52 (TokenSerializationType, Cow<'i, str>, TokenSerializationType),
53 ParseError<'i, CustomParseError<'i>>,
54 > {
55 let start_position = input.position();
56 let mut missing_closing_characters = String::new();
57 let (first, last) = Self::parse_declaration_value(
58 input,
59 references,
60 &mut missing_closing_characters,
61 )?;
62 let mut css: Cow<str> = input.slice_from(start_position).into();
63 if !missing_closing_characters.is_empty() {
64 if css.ends_with('\\') {
66 let first = missing_closing_characters.as_bytes()[0];
67 if first == b'"' || first == b'\'' {
68 css.to_mut().pop();
69 }
70 }
71 css.to_mut().push_str(&missing_closing_characters);
72 }
73 Ok((first, css, last))
74 }
75
76 fn parse_declaration_value<'i, 't>(
78 input: &mut Parser<'i, 't>,
79 references: &mut Option<HashSet<Atom>>,
80 missing_closing_characters: &mut String,
81 ) -> Result<
82 (TokenSerializationType, TokenSerializationType),
83 ParseError<'i, CustomParseError<'i>>,
84 > {
85 input.parse_until_before(
86 Delimiter::Bang | Delimiter::Semicolon,
87 |input| {
88 let start = input.state();
90 input.next_including_whitespace()?;
91 input.reset(&start);
92
93 Self::parse_declaration_value_block(
94 input,
95 references,
96 missing_closing_characters,
97 )
98 },
99 )
100 }
101
102 fn parse_declaration_value_block<'i, 't>(
104 input: &mut Parser<'i, 't>,
105 references: &mut Option<HashSet<Atom>>,
106 missing_closing_characters: &mut String,
107 ) -> Result<
108 (TokenSerializationType, TokenSerializationType),
109 ParseError<'i, CustomParseError<'i>>,
110 > {
111 let mut token_start = input.position();
112 let mut token = match input.next_including_whitespace_and_comments() {
113 Ok(token) => token.clone(),
114 Err(_) => {
115 return Ok((
116 TokenSerializationType::nothing(),
117 TokenSerializationType::nothing(),
118 ))
119 }
120 };
121
122 let first_token_type = token.serialization_type();
123 loop {
124 macro_rules! nested {
125 () => {
126 input.parse_nested_block(|input| {
127 Self::parse_declaration_value_block(
128 input,
129 references,
130 missing_closing_characters,
131 )
132 })?
133 };
134 }
135
136 macro_rules! check_closed {
137 ($closing: expr) => {
138 if !input.slice_from(token_start).ends_with($closing) {
139 missing_closing_characters.push_str($closing)
140 }
141 };
142 }
143
144 use cssparser::Token::*;
145
146 let last_token_type = match token {
147 Comment(_) => {
148 let token_slice = input.slice_from(token_start);
149 if !token_slice.ends_with("*/") {
150 missing_closing_characters.push_str(if token_slice.ends_with('*') {
151 "/"
152 } else {
153 "*/"
154 })
155 }
156 token.serialization_type()
157 }
158
159 BadUrl(url) => {
160 return Err(ParseError::from(
161 CustomParseError::BadUrlInDeclarationValueBlock(url),
162 ))
163 }
164
165 BadString(string) => {
166 return Err(ParseError::from(
167 CustomParseError::BadStringInDeclarationValueBlock(string),
168 ))
169 }
170
171 CloseParenthesis => {
172 return Err(ParseError::from(
173 CustomParseError::UnbalancedCloseParenthesisInDeclarationValueBlock,
174 ))
175 }
176
177 CloseSquareBracket => {
178 return Err(ParseError::from(
179 CustomParseError::UnbalancedCloseSquareBracketInDeclarationValueBlock,
180 ))
181 }
182
183 CloseCurlyBracket => {
184 return Err(ParseError::from(
185 CustomParseError::UnbalancedCloseCurlyBracketInDeclarationValueBlock,
186 ))
187 }
188
189 Function(ref name) => {
190 if name.eq_ignore_ascii_case("var") {
191 let args_start = input.state();
192 input.parse_nested_block(|input| {
193 Self::parse_var_function(input, references)
194 })?;
195 input.reset(&args_start);
196 }
197 nested!();
198 check_closed!(")");
199 CloseParenthesis.serialization_type()
200 }
201
202 ParenthesisBlock => {
203 nested!();
204 check_closed!(")");
205 CloseParenthesis.serialization_type()
206 }
207
208 CurlyBracketBlock => {
209 nested!();
210 check_closed!("}");
211 CloseCurlyBracket.serialization_type()
212 }
213
214 SquareBracketBlock => {
215 nested!();
216 check_closed!("]");
217 CloseSquareBracket.serialization_type()
218 }
219
220 QuotedString(_) => {
221 let token_slice = input.slice_from(token_start);
222 let quote = &token_slice[..1];
223 if !(token_slice.ends_with(quote) && token_slice.len() > 1) {
224 missing_closing_characters.push_str(quote)
225 }
226 token.serialization_type()
227 }
228
229 Ident(ref value)
230 | AtKeyword(ref value)
231 | Hash(ref value)
232 | IDHash(ref value)
233 | UnquotedUrl(ref value)
234 | Dimension {
235 unit: ref value, ..
236 } => {
237 if value.ends_with('�') && input.slice_from(token_start).ends_with('\\') {
238 missing_closing_characters.push('�')
243 }
244
245 match token {
246 UnquotedUrl(_) => check_closed!(")"),
247 _ => {}
248 }
249
250 token.serialization_type()
251 }
252 _ => token.serialization_type(),
253 };
254
255 token_start = input.position();
256 token = match input.next_including_whitespace_and_comments() {
257 Ok(token) => token.clone(),
258 Err(..) => return Ok((first_token_type, last_token_type)),
259 };
260 }
261 }
262
263 fn parse_var_function<'i, 't>(
265 input: &mut Parser<'i, 't>,
266 references: &mut Option<HashSet<Atom>>,
267 ) -> Result<(), ParseError<'i, CustomParseError<'i>>> {
268 let name = input.expect_ident_cloned()?;
269 if input.r#try(|input| input.expect_comma()).is_ok() {
270 input.parse_until_before(
273 Delimiter::Bang | Delimiter::Semicolon,
274 |input| {
275 input.next_including_whitespace()?;
277
278 while input.next_including_whitespace_and_comments().is_ok()
280 {
281 }
282
283 Ok(())
284 },
285 )?;
286 }
287 if let Some(ref mut refs) = *references {
288 refs.insert(Atom::from(name));
289 }
290 Ok(())
291 }
292}