1use alloc::{collections::BTreeMap, string::ToString, vec::Vec};
3use core::{fmt, num::ParseIntError};
4
5pub use azul_simplecss::Error as CssSyntaxError;
6use azul_simplecss::Tokenizer;
7
8pub use crate::props::property::CssParsingError;
9use crate::{
10 corety::AzString,
11 css::{
12 Css, CssDeclaration, CssNthChildSelector, CssPath, CssPathPseudoSelector, CssPathSelector,
13 CssRuleBlock, DynamicCssProperty, NodeTypeTag, NodeTypeTagParseError,
14 NodeTypeTagParseErrorOwned, Stylesheet,
15 },
16 dynamic_selector::{
17 DynamicSelector, DynamicSelectorVec, LanguageCondition, MediaType, MinMaxRange,
18 OrientationType,
19 },
20 props::{
21 basic::parse::parse_parentheses,
22 property::{
23 parse_combined_css_property, parse_css_property, CombinedCssPropertyType, CssKeyMap,
24 CssParsingErrorOwned, CssPropertyType,
25 },
26 },
27};
28
29#[derive(Debug, Clone, PartialEq)]
31pub struct CssParseError<'a> {
32 pub css_string: &'a str,
33 pub error: CssParseErrorInner<'a>,
34 pub location: (ErrorLocation, ErrorLocation),
35}
36
37#[derive(Debug, Clone, PartialEq)]
39pub struct CssParseErrorOwned {
40 pub css_string: String,
41 pub error: CssParseErrorInnerOwned,
42 pub location: (ErrorLocation, ErrorLocation),
43}
44
45impl<'a> CssParseError<'a> {
46 pub fn to_contained(&self) -> CssParseErrorOwned {
47 CssParseErrorOwned {
48 css_string: self.css_string.to_string(),
49 error: self.error.to_contained(),
50 location: self.location.clone(),
51 }
52 }
53}
54
55impl CssParseErrorOwned {
56 pub fn to_shared<'a>(&'a self) -> CssParseError<'a> {
57 CssParseError {
58 css_string: &self.css_string,
59 error: self.error.to_shared(),
60 location: self.location.clone(),
61 }
62 }
63}
64
65impl<'a> CssParseError<'a> {
66 pub fn get_error_string(&self) -> &'a str {
68 let (start, end) = (self.location.0.original_pos, self.location.1.original_pos);
69 let s = &self.css_string[start..end];
70 s.trim()
71 }
72}
73
74#[derive(Debug, Clone, PartialEq)]
75pub enum CssParseErrorInner<'a> {
76 ParseError(CssSyntaxError),
78 UnclosedBlock,
80 MalformedCss,
82 DynamicCssParseError(DynamicCssParseError<'a>),
85 PseudoSelectorParseError(CssPseudoSelectorParseError<'a>),
87 NodeTypeTag(NodeTypeTagParseError<'a>),
89 UnknownPropertyKey(&'a str, &'a str),
92 VarOnShorthandProperty {
98 key: CombinedCssPropertyType,
99 value: &'a str,
100 },
101}
102
103#[derive(Debug, Clone, PartialEq)]
104pub enum CssParseErrorInnerOwned {
105 ParseError(CssSyntaxError),
106 UnclosedBlock,
107 MalformedCss,
108 DynamicCssParseError(DynamicCssParseErrorOwned),
109 PseudoSelectorParseError(CssPseudoSelectorParseErrorOwned),
110 NodeTypeTag(NodeTypeTagParseErrorOwned),
111 UnknownPropertyKey(String, String),
112 VarOnShorthandProperty {
113 key: CombinedCssPropertyType,
114 value: String,
115 },
116}
117
118impl<'a> CssParseErrorInner<'a> {
119 pub fn to_contained(&self) -> CssParseErrorInnerOwned {
120 match self {
121 CssParseErrorInner::ParseError(e) => CssParseErrorInnerOwned::ParseError(e.clone()),
122 CssParseErrorInner::UnclosedBlock => CssParseErrorInnerOwned::UnclosedBlock,
123 CssParseErrorInner::MalformedCss => CssParseErrorInnerOwned::MalformedCss,
124 CssParseErrorInner::DynamicCssParseError(e) => {
125 CssParseErrorInnerOwned::DynamicCssParseError(e.to_contained())
126 }
127 CssParseErrorInner::PseudoSelectorParseError(e) => {
128 CssParseErrorInnerOwned::PseudoSelectorParseError(e.to_contained())
129 }
130 CssParseErrorInner::NodeTypeTag(e) => {
131 CssParseErrorInnerOwned::NodeTypeTag(e.to_contained())
132 }
133 CssParseErrorInner::UnknownPropertyKey(a, b) => {
134 CssParseErrorInnerOwned::UnknownPropertyKey(a.to_string(), b.to_string())
135 }
136 CssParseErrorInner::VarOnShorthandProperty { key, value } => {
137 CssParseErrorInnerOwned::VarOnShorthandProperty {
138 key: key.clone(),
139 value: value.to_string(),
140 }
141 }
142 }
143 }
144}
145
146impl CssParseErrorInnerOwned {
147 pub fn to_shared<'a>(&'a self) -> CssParseErrorInner<'a> {
148 match self {
149 CssParseErrorInnerOwned::ParseError(e) => CssParseErrorInner::ParseError(e.clone()),
150 CssParseErrorInnerOwned::UnclosedBlock => CssParseErrorInner::UnclosedBlock,
151 CssParseErrorInnerOwned::MalformedCss => CssParseErrorInner::MalformedCss,
152 CssParseErrorInnerOwned::DynamicCssParseError(e) => {
153 CssParseErrorInner::DynamicCssParseError(e.to_shared())
154 }
155 CssParseErrorInnerOwned::PseudoSelectorParseError(e) => {
156 CssParseErrorInner::PseudoSelectorParseError(e.to_shared())
157 }
158 CssParseErrorInnerOwned::NodeTypeTag(e) => {
159 CssParseErrorInner::NodeTypeTag(e.to_shared())
160 }
161 CssParseErrorInnerOwned::UnknownPropertyKey(a, b) => {
162 CssParseErrorInner::UnknownPropertyKey(a, b)
163 }
164 CssParseErrorInnerOwned::VarOnShorthandProperty { key, value } => {
165 CssParseErrorInner::VarOnShorthandProperty {
166 key: key.clone(),
167 value,
168 }
169 }
170 }
171 }
172}
173
174impl_display! { CssParseErrorInner<'a>, {
175 ParseError(e) => format!("Parse Error: {:?}", e),
176 UnclosedBlock => "Unclosed block",
177 MalformedCss => "Malformed Css",
178 DynamicCssParseError(e) => format!("{}", e),
179 PseudoSelectorParseError(e) => format!("Failed to parse pseudo-selector: {}", e),
180 NodeTypeTag(e) => format!("Failed to parse CSS selector path: {}", e),
181 UnknownPropertyKey(k, v) => format!("Unknown CSS key: \"{}: {}\"", k, v),
182 VarOnShorthandProperty { key, value } => format!(
183 "Error while parsing: \"{}: {};\": var() cannot be used on shorthand properties - use `{}-top` or `{}-x` as the key instead: ",
184 key, value, key, key
185 ),
186}}
187
188impl<'a> From<CssSyntaxError> for CssParseErrorInner<'a> {
189 fn from(e: CssSyntaxError) -> Self {
190 CssParseErrorInner::ParseError(e)
191 }
192}
193
194impl_from! { DynamicCssParseError<'a>, CssParseErrorInner::DynamicCssParseError }
195impl_from! { NodeTypeTagParseError<'a>, CssParseErrorInner::NodeTypeTag }
196impl_from! { CssPseudoSelectorParseError<'a>, CssParseErrorInner::PseudoSelectorParseError }
197
198#[derive(Debug, Clone, PartialEq, Eq)]
199pub enum CssPseudoSelectorParseError<'a> {
200 EmptyNthChild,
201 UnknownSelector(&'a str, Option<&'a str>),
202 InvalidNthChildPattern(&'a str),
203 InvalidNthChild(ParseIntError),
204}
205
206impl<'a> From<ParseIntError> for CssPseudoSelectorParseError<'a> {
207 fn from(e: ParseIntError) -> Self {
208 CssPseudoSelectorParseError::InvalidNthChild(e)
209 }
210}
211
212impl_display! { CssPseudoSelectorParseError<'a>, {
213 EmptyNthChild => format!("\
214 Empty :nth-child() selector - nth-child() must at least take a number, \
215 a pattern (such as \"2n+3\") or the values \"even\" or \"odd\"."
216 ),
217 UnknownSelector(selector, value) => {
218 let format_str = match value {
219 Some(v) => format!("{}({})", selector, v),
220 None => format!("{}", selector),
221 };
222 format!("Invalid or unknown CSS pseudo-selector: ':{}'", format_str)
223 },
224 InvalidNthChildPattern(selector) => format!(
225 "Invalid pseudo-selector :{} - value has to be a \
226 number, \"even\" or \"odd\" or a pattern such as \"2n+3\"", selector
227 ),
228 InvalidNthChild(e) => format!("Invalid :nth-child pseudo-selector: ':{}'", e),
229}}
230
231#[derive(Debug, Clone, PartialEq)]
232pub enum CssPseudoSelectorParseErrorOwned {
233 EmptyNthChild,
234 UnknownSelector(String, Option<String>),
235 InvalidNthChildPattern(String),
236 InvalidNthChild(ParseIntError),
237}
238
239impl<'a> CssPseudoSelectorParseError<'a> {
240 pub fn to_contained(&self) -> CssPseudoSelectorParseErrorOwned {
241 match self {
242 CssPseudoSelectorParseError::EmptyNthChild => {
243 CssPseudoSelectorParseErrorOwned::EmptyNthChild
244 }
245 CssPseudoSelectorParseError::UnknownSelector(a, b) => {
246 CssPseudoSelectorParseErrorOwned::UnknownSelector(
247 a.to_string(),
248 b.map(|s| s.to_string()),
249 )
250 }
251 CssPseudoSelectorParseError::InvalidNthChildPattern(s) => {
252 CssPseudoSelectorParseErrorOwned::InvalidNthChildPattern(s.to_string())
253 }
254 CssPseudoSelectorParseError::InvalidNthChild(e) => {
255 CssPseudoSelectorParseErrorOwned::InvalidNthChild(e.clone())
256 }
257 }
258 }
259}
260
261impl CssPseudoSelectorParseErrorOwned {
262 pub fn to_shared<'a>(&'a self) -> CssPseudoSelectorParseError<'a> {
263 match self {
264 CssPseudoSelectorParseErrorOwned::EmptyNthChild => {
265 CssPseudoSelectorParseError::EmptyNthChild
266 }
267 CssPseudoSelectorParseErrorOwned::UnknownSelector(a, b) => {
268 CssPseudoSelectorParseError::UnknownSelector(a, b.as_deref())
269 }
270 CssPseudoSelectorParseErrorOwned::InvalidNthChildPattern(s) => {
271 CssPseudoSelectorParseError::InvalidNthChildPattern(s)
272 }
273 CssPseudoSelectorParseErrorOwned::InvalidNthChild(e) => {
274 CssPseudoSelectorParseError::InvalidNthChild(e.clone())
275 }
276 }
277 }
278}
279
280#[derive(Debug, Clone, PartialEq)]
282pub enum DynamicCssParseError<'a> {
283 InvalidBraceContents(&'a str),
285 UnexpectedValue(CssParsingError<'a>),
287}
288
289impl_display! { DynamicCssParseError<'a>, {
290 InvalidBraceContents(e) => format!("Invalid contents of var() function: var({})", e),
291 UnexpectedValue(e) => format!("{}", e),
292}}
293
294impl<'a> From<CssParsingError<'a>> for DynamicCssParseError<'a> {
295 fn from(e: CssParsingError<'a>) -> Self {
296 DynamicCssParseError::UnexpectedValue(e)
297 }
298}
299
300#[derive(Debug, Clone, PartialEq)]
301pub enum DynamicCssParseErrorOwned {
302 InvalidBraceContents(String),
303 UnexpectedValue(CssParsingErrorOwned),
304}
305
306impl<'a> DynamicCssParseError<'a> {
307 pub fn to_contained(&self) -> DynamicCssParseErrorOwned {
308 match self {
309 DynamicCssParseError::InvalidBraceContents(s) => {
310 DynamicCssParseErrorOwned::InvalidBraceContents(s.to_string())
311 }
312 DynamicCssParseError::UnexpectedValue(e) => {
313 DynamicCssParseErrorOwned::UnexpectedValue(e.to_contained())
314 }
315 }
316 }
317}
318
319impl DynamicCssParseErrorOwned {
320 pub fn to_shared<'a>(&'a self) -> DynamicCssParseError<'a> {
321 match self {
322 DynamicCssParseErrorOwned::InvalidBraceContents(s) => {
323 DynamicCssParseError::InvalidBraceContents(s)
324 }
325 DynamicCssParseErrorOwned::UnexpectedValue(e) => {
326 DynamicCssParseError::UnexpectedValue(e.to_shared())
327 }
328 }
329 }
330}
331
332pub fn pseudo_selector_from_str<'a>(
335 selector: &'a str,
336 value: Option<&'a str>,
337) -> Result<CssPathPseudoSelector, CssPseudoSelectorParseError<'a>> {
338 match selector {
339 "first" => Ok(CssPathPseudoSelector::First),
340 "last" => Ok(CssPathPseudoSelector::Last),
341 "hover" => Ok(CssPathPseudoSelector::Hover),
342 "active" => Ok(CssPathPseudoSelector::Active),
343 "focus" => Ok(CssPathPseudoSelector::Focus),
344 "nth-child" => {
345 let value = value.ok_or(CssPseudoSelectorParseError::EmptyNthChild)?;
346 let parsed = parse_nth_child_selector(value)?;
347 Ok(CssPathPseudoSelector::NthChild(parsed))
348 }
349 "lang" => {
350 let lang_value = value.ok_or(CssPseudoSelectorParseError::UnknownSelector(
351 selector, value,
352 ))?;
353 let lang_value = lang_value
355 .trim()
356 .trim_start_matches('"')
357 .trim_end_matches('"')
358 .trim_start_matches('\'')
359 .trim_end_matches('\'')
360 .trim();
361 Ok(CssPathPseudoSelector::Lang(AzString::from(
362 lang_value.to_string(),
363 )))
364 }
365 _ => Err(CssPseudoSelectorParseError::UnknownSelector(
366 selector, value,
367 )),
368 }
369}
370
371fn parse_nth_child_selector<'a>(
375 value: &'a str,
376) -> Result<CssNthChildSelector, CssPseudoSelectorParseError<'a>> {
377 let value = value.trim();
378
379 if value.is_empty() {
380 return Err(CssPseudoSelectorParseError::EmptyNthChild);
381 }
382
383 if let Ok(number) = value.parse::<u32>() {
384 return Ok(CssNthChildSelector::Number(number));
385 }
386
387 match value.as_ref() {
389 "even" => Ok(CssNthChildSelector::Even),
390 "odd" => Ok(CssNthChildSelector::Odd),
391 other => parse_nth_child_pattern(value),
392 }
393}
394
395fn parse_nth_child_pattern<'a>(
397 value: &'a str,
398) -> Result<CssNthChildSelector, CssPseudoSelectorParseError<'a>> {
399 use crate::css::CssNthChildPattern;
400
401 let value = value.trim();
402
403 if value.is_empty() {
404 return Err(CssPseudoSelectorParseError::EmptyNthChild);
405 }
406
407 let repeat = value
409 .split("n")
410 .next()
411 .ok_or(CssPseudoSelectorParseError::InvalidNthChildPattern(value))?
412 .trim()
413 .parse::<u32>()?;
414
415 let mut offset_iterator = value.split("+");
417
418 offset_iterator.next().unwrap();
420
421 let offset = match offset_iterator.next() {
422 Some(offset_string) => {
423 let offset_string = offset_string.trim();
424 if offset_string.is_empty() {
425 return Err(CssPseudoSelectorParseError::InvalidNthChildPattern(value));
426 } else {
427 offset_string.parse::<u32>()?
428 }
429 }
430 None => 0,
431 };
432
433 Ok(CssNthChildSelector::Pattern(CssNthChildPattern {
434 pattern_repeat: repeat,
435 offset,
436 }))
437}
438
439#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
440pub struct ErrorLocation {
441 pub original_pos: usize,
442}
443
444impl ErrorLocation {
445 pub fn get_line_column_from_error(&self, css_string: &str) -> (usize, usize) {
447 let error_location = self.original_pos.saturating_sub(1);
448 let (mut line_number, mut total_characters) = (0, 0);
449
450 for line in css_string[0..error_location].lines() {
451 line_number += 1;
452 total_characters += line.chars().count();
453 }
454
455 let total_characters = total_characters + line_number;
457 let column_pos = error_location - total_characters.saturating_sub(2);
458
459 (line_number, column_pos)
460 }
461}
462
463impl<'a> fmt::Display for CssParseError<'a> {
464 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
465 let start_location = self.location.0.get_line_column_from_error(self.css_string);
466 let end_location = self.location.1.get_line_column_from_error(self.css_string);
467 write!(
468 f,
469 " start: line {}:{}\r\n end: line {}:{}\r\n text: \"{}\"\r\n reason: {}",
470 start_location.0,
471 start_location.1,
472 end_location.0,
473 end_location.1,
474 self.get_error_string(),
475 self.error,
476 )
477 }
478}
479
480pub fn new_from_str<'a>(css_string: &'a str) -> (Css, Vec<CssParseWarnMsg<'a>>) {
481 let mut tokenizer = Tokenizer::new(css_string);
482 let (stylesheet, warnings) = match new_from_str_inner(css_string, &mut tokenizer) {
483 Ok((stylesheet, warnings)) => (stylesheet, warnings),
484 Err(error) => {
485 let warning = CssParseWarnMsg {
486 warning: CssParseWarnMsgInner::ParseError(error.error),
487 location: error.location,
488 };
489 (Stylesheet::default(), vec![warning])
490 }
491 };
492
493 (
494 Css {
495 stylesheets: vec![stylesheet].into(),
496 },
497 warnings,
498 )
499}
500
501fn get_error_location(tokenizer: &Tokenizer) -> ErrorLocation {
503 ErrorLocation {
504 original_pos: tokenizer.pos(),
505 }
506}
507
508#[derive(Debug, Clone, PartialEq)]
509pub enum CssPathParseError<'a> {
510 EmptyPath,
511 InvalidTokenEncountered(&'a str),
513 UnexpectedEndOfStream(&'a str),
514 SyntaxError(CssSyntaxError),
515 NodeTypeTag(NodeTypeTagParseError<'a>),
517 PseudoSelectorParseError(CssPseudoSelectorParseError<'a>),
519}
520
521impl_from! { NodeTypeTagParseError<'a>, CssPathParseError::NodeTypeTag }
522impl_from! { CssPseudoSelectorParseError<'a>, CssPathParseError::PseudoSelectorParseError }
523
524impl<'a> From<CssSyntaxError> for CssPathParseError<'a> {
525 fn from(e: CssSyntaxError) -> Self {
526 CssPathParseError::SyntaxError(e)
527 }
528}
529
530#[derive(Debug, Clone, PartialEq)]
531pub enum CssPathParseErrorOwned {
532 EmptyPath,
533 InvalidTokenEncountered(String),
534 UnexpectedEndOfStream(String),
535 SyntaxError(CssSyntaxError),
536 NodeTypeTag(NodeTypeTagParseErrorOwned),
537 PseudoSelectorParseError(CssPseudoSelectorParseErrorOwned),
538}
539
540impl<'a> CssPathParseError<'a> {
541 pub fn to_contained(&self) -> CssPathParseErrorOwned {
542 match self {
543 CssPathParseError::EmptyPath => CssPathParseErrorOwned::EmptyPath,
544 CssPathParseError::InvalidTokenEncountered(s) => {
545 CssPathParseErrorOwned::InvalidTokenEncountered(s.to_string())
546 }
547 CssPathParseError::UnexpectedEndOfStream(s) => {
548 CssPathParseErrorOwned::UnexpectedEndOfStream(s.to_string())
549 }
550 CssPathParseError::SyntaxError(e) => CssPathParseErrorOwned::SyntaxError(e.clone()),
551 CssPathParseError::NodeTypeTag(e) => {
552 CssPathParseErrorOwned::NodeTypeTag(e.to_contained())
553 }
554 CssPathParseError::PseudoSelectorParseError(e) => {
555 CssPathParseErrorOwned::PseudoSelectorParseError(e.to_contained())
556 }
557 }
558 }
559}
560
561impl CssPathParseErrorOwned {
562 pub fn to_shared<'a>(&'a self) -> CssPathParseError<'a> {
563 match self {
564 CssPathParseErrorOwned::EmptyPath => CssPathParseError::EmptyPath,
565 CssPathParseErrorOwned::InvalidTokenEncountered(s) => {
566 CssPathParseError::InvalidTokenEncountered(s)
567 }
568 CssPathParseErrorOwned::UnexpectedEndOfStream(s) => {
569 CssPathParseError::UnexpectedEndOfStream(s)
570 }
571 CssPathParseErrorOwned::SyntaxError(e) => CssPathParseError::SyntaxError(e.clone()),
572 CssPathParseErrorOwned::NodeTypeTag(e) => CssPathParseError::NodeTypeTag(e.to_shared()),
573 CssPathParseErrorOwned::PseudoSelectorParseError(e) => {
574 CssPathParseError::PseudoSelectorParseError(e.to_shared())
575 }
576 }
577 }
578}
579
580pub fn parse_css_path<'a>(input: &'a str) -> Result<CssPath, CssPathParseError<'a>> {
607 use azul_simplecss::{Combinator, Token};
608
609 let input = input.trim();
610 if input.is_empty() {
611 return Err(CssPathParseError::EmptyPath);
612 }
613
614 let mut tokenizer = Tokenizer::new(input);
615 let mut selectors = Vec::new();
616
617 loop {
618 let token = tokenizer.parse_next()?;
619 match token {
620 Token::UniversalSelector => {
621 selectors.push(CssPathSelector::Global);
622 }
623 Token::TypeSelector(div_type) => {
624 if let Ok(nt) = NodeTypeTag::from_str(div_type) {
625 selectors.push(CssPathSelector::Type(nt));
626 }
627 }
628 Token::IdSelector(id) => {
629 selectors.push(CssPathSelector::Id(id.to_string().into()));
630 }
631 Token::ClassSelector(class) => {
632 selectors.push(CssPathSelector::Class(class.to_string().into()));
633 }
634 Token::Combinator(Combinator::GreaterThan) => {
635 selectors.push(CssPathSelector::DirectChildren);
636 }
637 Token::Combinator(Combinator::Space) => {
638 selectors.push(CssPathSelector::Children);
639 }
640 Token::Combinator(Combinator::Plus) => {
641 selectors.push(CssPathSelector::AdjacentSibling);
642 }
643 Token::Combinator(Combinator::Tilde) => {
644 selectors.push(CssPathSelector::GeneralSibling);
645 }
646 Token::PseudoClass { selector, value } => {
647 selectors.push(CssPathSelector::PseudoSelector(pseudo_selector_from_str(
648 selector, value,
649 )?));
650 }
651 Token::EndOfStream => {
652 break;
653 }
654 _ => {
655 return Err(CssPathParseError::InvalidTokenEncountered(input));
656 }
657 }
658 }
659
660 if !selectors.is_empty() {
661 Ok(CssPath {
662 selectors: selectors.into(),
663 })
664 } else {
665 Err(CssPathParseError::EmptyPath)
666 }
667}
668
669#[derive(Debug, Clone, PartialEq)]
670pub struct UnparsedCssRuleBlock<'a> {
671 pub path: CssPath,
673 pub declarations: BTreeMap<&'a str, (&'a str, (ErrorLocation, ErrorLocation))>,
675 pub conditions: Vec<DynamicSelector>,
677}
678
679#[derive(Debug, Clone, PartialEq)]
681pub struct UnparsedCssRuleBlockOwned {
682 pub path: CssPath,
683 pub declarations: BTreeMap<String, (String, (ErrorLocation, ErrorLocation))>,
684 pub conditions: Vec<DynamicSelector>,
685}
686
687impl<'a> UnparsedCssRuleBlock<'a> {
688 pub fn to_contained(&self) -> UnparsedCssRuleBlockOwned {
689 UnparsedCssRuleBlockOwned {
690 path: self.path.clone(),
691 declarations: self
692 .declarations
693 .iter()
694 .map(|(k, (v, loc))| (k.to_string(), (v.to_string(), loc.clone())))
695 .collect(),
696 conditions: self.conditions.clone(),
697 }
698 }
699}
700
701impl UnparsedCssRuleBlockOwned {
702 pub fn to_shared<'a>(&'a self) -> UnparsedCssRuleBlock<'a> {
703 UnparsedCssRuleBlock {
704 path: self.path.clone(),
705 declarations: self
706 .declarations
707 .iter()
708 .map(|(k, (v, loc))| (k.as_str(), (v.as_str(), loc.clone())))
709 .collect(),
710 conditions: self.conditions.clone(),
711 }
712 }
713}
714
715#[derive(Debug, Clone, PartialEq)]
716pub struct CssParseWarnMsg<'a> {
717 pub warning: CssParseWarnMsgInner<'a>,
718 pub location: (ErrorLocation, ErrorLocation),
719}
720
721#[derive(Debug, Clone, PartialEq)]
723pub struct CssParseWarnMsgOwned {
724 pub warning: CssParseWarnMsgInnerOwned,
725 pub location: (ErrorLocation, ErrorLocation),
726}
727
728impl<'a> CssParseWarnMsg<'a> {
729 pub fn to_contained(&self) -> CssParseWarnMsgOwned {
730 CssParseWarnMsgOwned {
731 warning: self.warning.to_contained(),
732 location: self.location.clone(),
733 }
734 }
735}
736
737impl CssParseWarnMsgOwned {
738 pub fn to_shared<'a>(&'a self) -> CssParseWarnMsg<'a> {
739 CssParseWarnMsg {
740 warning: self.warning.to_shared(),
741 location: self.location.clone(),
742 }
743 }
744}
745
746#[derive(Debug, Clone, PartialEq)]
747pub enum CssParseWarnMsgInner<'a> {
748 UnsupportedKeyValuePair { key: &'a str, value: &'a str },
750 ParseError(CssParseErrorInner<'a>),
752 SkippedRule {
754 selector: Option<&'a str>,
755 error: CssParseErrorInner<'a>,
756 },
757 SkippedDeclaration {
759 key: &'a str,
760 value: &'a str,
761 error: CssParseErrorInner<'a>,
762 },
763 MalformedStructure { message: &'a str },
765}
766
767#[derive(Debug, Clone, PartialEq)]
768pub enum CssParseWarnMsgInnerOwned {
769 UnsupportedKeyValuePair {
770 key: String,
771 value: String,
772 },
773 ParseError(CssParseErrorInnerOwned),
774 SkippedRule {
775 selector: Option<String>,
776 error: CssParseErrorInnerOwned,
777 },
778 SkippedDeclaration {
779 key: String,
780 value: String,
781 error: CssParseErrorInnerOwned,
782 },
783 MalformedStructure {
784 message: String,
785 },
786}
787
788impl<'a> CssParseWarnMsgInner<'a> {
789 pub fn to_contained(&self) -> CssParseWarnMsgInnerOwned {
790 match self {
791 Self::UnsupportedKeyValuePair { key, value } => {
792 CssParseWarnMsgInnerOwned::UnsupportedKeyValuePair {
793 key: key.to_string(),
794 value: value.to_string(),
795 }
796 }
797 Self::ParseError(e) => CssParseWarnMsgInnerOwned::ParseError(e.to_contained()),
798 Self::SkippedRule { selector, error } => CssParseWarnMsgInnerOwned::SkippedRule {
799 selector: selector.map(|s| s.to_string()),
800 error: error.to_contained(),
801 },
802 Self::SkippedDeclaration { key, value, error } => {
803 CssParseWarnMsgInnerOwned::SkippedDeclaration {
804 key: key.to_string(),
805 value: value.to_string(),
806 error: error.to_contained(),
807 }
808 }
809 Self::MalformedStructure { message } => CssParseWarnMsgInnerOwned::MalformedStructure {
810 message: message.to_string(),
811 },
812 }
813 }
814}
815
816impl CssParseWarnMsgInnerOwned {
817 pub fn to_shared<'a>(&'a self) -> CssParseWarnMsgInner<'a> {
818 match self {
819 Self::UnsupportedKeyValuePair { key, value } => {
820 CssParseWarnMsgInner::UnsupportedKeyValuePair { key, value }
821 }
822 Self::ParseError(e) => CssParseWarnMsgInner::ParseError(e.to_shared()),
823 Self::SkippedRule { selector, error } => CssParseWarnMsgInner::SkippedRule {
824 selector: selector.as_deref(),
825 error: error.to_shared(),
826 },
827 Self::SkippedDeclaration { key, value, error } => {
828 CssParseWarnMsgInner::SkippedDeclaration {
829 key,
830 value,
831 error: error.to_shared(),
832 }
833 }
834 Self::MalformedStructure { message } => {
835 CssParseWarnMsgInner::MalformedStructure { message }
836 }
837 }
838 }
839}
840
841impl_display! { CssParseWarnMsgInner<'a>, {
842 UnsupportedKeyValuePair { key, value } => format!("Unsupported CSS property: \"{}: {}\"", key, value),
843 ParseError(e) => format!("Parse error (recoverable): {}", e),
844 SkippedRule { selector, error } => {
845 let sel = selector.unwrap_or("unknown");
846 format!("Skipped rule for selector '{}': {}", sel, error)
847 },
848 SkippedDeclaration { key, value, error } => format!("Skipped declaration '{}:{}': {}", key, value, error),
849 MalformedStructure { message } => format!("Malformed CSS structure: {}", message),
850}}
851
852fn parse_media_conditions(content: &str) -> Vec<DynamicSelector> {
855 let mut conditions = Vec::new();
856 let content = content.trim();
857
858 if content.eq_ignore_ascii_case("screen") {
860 conditions.push(DynamicSelector::Media(MediaType::Screen));
861 return conditions;
862 }
863 if content.eq_ignore_ascii_case("print") {
864 conditions.push(DynamicSelector::Media(MediaType::Print));
865 return conditions;
866 }
867 if content.eq_ignore_ascii_case("all") {
868 conditions.push(DynamicSelector::Media(MediaType::All));
869 return conditions;
870 }
871
872 for part in content.split(" and ") {
875 let part = part.trim();
876
877 if part.eq_ignore_ascii_case("screen")
879 || part.eq_ignore_ascii_case("print")
880 || part.eq_ignore_ascii_case("all")
881 {
882 if part.eq_ignore_ascii_case("screen") {
883 conditions.push(DynamicSelector::Media(MediaType::Screen));
884 } else if part.eq_ignore_ascii_case("print") {
885 conditions.push(DynamicSelector::Media(MediaType::Print));
886 }
887 continue;
888 }
889
890 if let Some(inner) = part.strip_prefix('(').and_then(|s| s.strip_suffix(')')) {
892 if let Some(selector) = parse_media_feature(inner) {
893 conditions.push(selector);
894 }
895 }
896 }
897
898 conditions
899}
900
901fn parse_media_feature(feature: &str) -> Option<DynamicSelector> {
903 let parts: Vec<&str> = feature.splitn(2, ':').collect();
904 if parts.len() != 2 {
905 return None;
907 }
908
909 let key = parts[0].trim();
910 let value = parts[1].trim();
911
912 match key.to_lowercase().as_str() {
913 "min-width" => {
914 if let Some(px) = parse_px_value(value) {
915 return Some(DynamicSelector::ViewportWidth(MinMaxRange::new(
916 Some(px),
917 None,
918 )));
919 }
920 }
921 "max-width" => {
922 if let Some(px) = parse_px_value(value) {
923 return Some(DynamicSelector::ViewportWidth(MinMaxRange::new(
924 None,
925 Some(px),
926 )));
927 }
928 }
929 "min-height" => {
930 if let Some(px) = parse_px_value(value) {
931 return Some(DynamicSelector::ViewportHeight(MinMaxRange::new(
932 Some(px),
933 None,
934 )));
935 }
936 }
937 "max-height" => {
938 if let Some(px) = parse_px_value(value) {
939 return Some(DynamicSelector::ViewportHeight(MinMaxRange::new(
940 None,
941 Some(px),
942 )));
943 }
944 }
945 "orientation" => {
946 if value.eq_ignore_ascii_case("portrait") {
947 return Some(DynamicSelector::Orientation(OrientationType::Portrait));
948 } else if value.eq_ignore_ascii_case("landscape") {
949 return Some(DynamicSelector::Orientation(OrientationType::Landscape));
950 }
951 }
952 _ => {}
953 }
954
955 None
956}
957
958fn parse_px_value(value: &str) -> Option<f32> {
960 let value = value.trim();
961 if let Some(num_str) = value.strip_suffix("px") {
962 num_str.trim().parse::<f32>().ok()
963 } else {
964 value.parse::<f32>().ok()
966 }
967}
968
969fn parse_lang_condition(content: &str) -> Option<DynamicSelector> {
972 let content = content.trim();
973
974 let lang = content
976 .strip_prefix('(')
977 .and_then(|s| s.strip_suffix(')'))
978 .unwrap_or(content)
979 .trim();
980
981 let lang = lang
982 .strip_prefix('"')
983 .and_then(|s| s.strip_suffix('"'))
984 .or_else(|| lang.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
985 .unwrap_or(lang)
986 .trim();
987
988 if lang.is_empty() {
989 return None;
990 }
991
992 Some(DynamicSelector::Language(LanguageCondition::Prefix(
994 AzString::from(lang.to_string()),
995 )))
996}
997
998fn new_from_str_inner<'a>(
1004 css_string: &'a str,
1005 tokenizer: &mut Tokenizer<'a>,
1006) -> Result<(Stylesheet, Vec<CssParseWarnMsg<'a>>), CssParseError<'a>> {
1007 use azul_simplecss::{Combinator, Token};
1008
1009 let mut css_blocks = Vec::new();
1010 let mut warnings = Vec::new();
1011
1012 let mut parser_in_block = false;
1013 let mut block_nesting = 0_usize;
1014 let mut current_paths = Vec::new();
1015 let mut current_rules = BTreeMap::<&str, (&str, (ErrorLocation, ErrorLocation))>::new();
1016 let mut last_path = Vec::new();
1017 let mut last_error_location = ErrorLocation { original_pos: 0 };
1018
1019 let mut at_rule_stack: Vec<(Vec<DynamicSelector>, usize)> = Vec::new();
1022 let mut pending_at_rule: Option<&str> = None;
1024
1025 let max_iterations = css_string.len().saturating_mul(10).max(1000);
1028 let mut iterations = 0_usize;
1029 let mut last_position = 0_usize;
1030 let mut stuck_count = 0_usize;
1031
1032 loop {
1033 iterations += 1;
1035 if iterations > max_iterations {
1036 warnings.push(CssParseWarnMsg {
1037 warning: CssParseWarnMsgInner::MalformedStructure {
1038 message: "Parser iteration limit exceeded - possible infinite loop",
1039 },
1040 location: (last_error_location, get_error_location(tokenizer)),
1041 });
1042 break;
1043 }
1044
1045 let current_position = tokenizer.pos();
1047 if current_position == last_position {
1048 stuck_count += 1;
1049 if stuck_count > 10 {
1050 warnings.push(CssParseWarnMsg {
1051 warning: CssParseWarnMsgInner::MalformedStructure {
1052 message: "Parser stuck - position not advancing",
1053 },
1054 location: (last_error_location, get_error_location(tokenizer)),
1055 });
1056 break;
1057 }
1058 } else {
1059 stuck_count = 0;
1060 last_position = current_position;
1061 }
1062
1063 let token = match tokenizer.parse_next() {
1064 Ok(token) => token,
1065 Err(e) => {
1066 let error_location = get_error_location(tokenizer);
1067 warnings.push(CssParseWarnMsg {
1068 warning: CssParseWarnMsgInner::ParseError(e.into()),
1069 location: (last_error_location, error_location),
1070 });
1071 break;
1073 }
1074 };
1075
1076 macro_rules! warn_and_continue {
1077 ($warning:expr) => {{
1078 warnings.push(CssParseWarnMsg {
1079 warning: $warning,
1080 location: (last_error_location, get_error_location(tokenizer)),
1081 });
1082 continue;
1083 }};
1084 }
1085
1086 match token {
1087 Token::AtRule(rule_name) => {
1088 pending_at_rule = Some(rule_name);
1090 }
1091 Token::AtStr(content) => {
1092 if let Some(rule_name) = pending_at_rule.take() {
1094 let conditions = match rule_name.to_lowercase().as_str() {
1095 "media" => parse_media_conditions(content),
1096 "lang" => parse_lang_condition(content).into_iter().collect(),
1097 _ => {
1098 Vec::new()
1100 }
1101 };
1102
1103 if !conditions.is_empty() {
1104 at_rule_stack.push((conditions, block_nesting + 1));
1106 }
1107 }
1108 }
1109 Token::BlockStart => {
1110 if pending_at_rule.is_some() {
1112 pending_at_rule = None;
1114 }
1115
1116 block_nesting += 1;
1117
1118 if !current_paths.is_empty() || !last_path.is_empty() {
1120 if parser_in_block {
1121 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1122 message: "Block start inside another block"
1123 });
1124 }
1125 parser_in_block = true;
1126 if !last_path.is_empty() {
1127 current_paths.push(last_path.clone());
1128 last_path.clear();
1129 }
1130 }
1131 }
1132 Token::Comma => {
1133 if parser_in_block {
1134 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1135 message: "Comma inside block"
1136 });
1137 }
1138 if !last_path.is_empty() {
1139 current_paths.push(last_path.clone());
1140 last_path.clear();
1141 } else {
1142 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1143 message: "Empty selector before comma"
1144 });
1145 }
1146 }
1147 Token::BlockEnd => {
1148 if block_nesting == 0 {
1149 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1150 message: "Block end without matching block start"
1151 });
1152 }
1153
1154 let current_conditions: Vec<DynamicSelector> = at_rule_stack
1156 .iter()
1157 .flat_map(|(conds, _)| conds.iter().cloned())
1158 .collect();
1159
1160 while let Some((_, level)) = at_rule_stack.last() {
1162 if *level >= block_nesting {
1163 at_rule_stack.pop();
1164 } else {
1165 break;
1166 }
1167 }
1168
1169 block_nesting = block_nesting.saturating_sub(1);
1170
1171 if !current_paths.is_empty() {
1173 parser_in_block = false;
1174 css_blocks.extend(current_paths.drain(..).map(|path| UnparsedCssRuleBlock {
1175 path: CssPath {
1176 selectors: path.into(),
1177 },
1178 declarations: current_rules.clone(),
1179 conditions: current_conditions.clone(),
1180 }));
1181 current_rules.clear();
1182 } else if parser_in_block {
1183 parser_in_block = false;
1185 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1186 message: "Block with no selectors"
1187 });
1188 }
1189 last_path.clear();
1192 }
1193 Token::UniversalSelector => {
1194 if parser_in_block {
1195 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1196 message: "Selector inside block"
1197 });
1198 }
1199 last_path.push(CssPathSelector::Global);
1200 }
1201 Token::TypeSelector(div_type) => {
1202 if parser_in_block {
1203 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1204 message: "Selector inside block"
1205 });
1206 }
1207
1208 match NodeTypeTag::from_str(div_type) {
1209 Ok(nt) => last_path.push(CssPathSelector::Type(nt)),
1210 Err(e) => {
1211 warn_and_continue!(CssParseWarnMsgInner::SkippedRule {
1212 selector: Some(div_type),
1213 error: e.into(),
1214 });
1215 }
1216 }
1217 }
1218 Token::IdSelector(id) => {
1219 if parser_in_block {
1220 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1221 message: "Selector inside block"
1222 });
1223 }
1224 last_path.push(CssPathSelector::Id(id.to_string().into()));
1225 }
1226 Token::ClassSelector(class) => {
1227 if parser_in_block {
1228 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1229 message: "Selector inside block"
1230 });
1231 }
1232 last_path.push(CssPathSelector::Class(class.to_string().into()));
1233 }
1234 Token::Combinator(Combinator::GreaterThan) => {
1235 if parser_in_block {
1236 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1237 message: "Selector inside block"
1238 });
1239 }
1240 last_path.push(CssPathSelector::DirectChildren);
1241 }
1242 Token::Combinator(Combinator::Space) => {
1243 if parser_in_block {
1244 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1245 message: "Selector inside block"
1246 });
1247 }
1248 last_path.push(CssPathSelector::Children);
1249 }
1250 Token::Combinator(Combinator::Plus) => {
1251 if parser_in_block {
1252 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1253 message: "Selector inside block"
1254 });
1255 }
1256 last_path.push(CssPathSelector::AdjacentSibling);
1257 }
1258 Token::Combinator(Combinator::Tilde) => {
1259 if parser_in_block {
1260 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1261 message: "Selector inside block"
1262 });
1263 }
1264 last_path.push(CssPathSelector::GeneralSibling);
1265 }
1266 Token::PseudoClass { selector, value } => {
1267 if parser_in_block {
1268 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1269 message: "Selector inside block"
1270 });
1271 }
1272
1273 match pseudo_selector_from_str(selector, value) {
1274 Ok(ps) => last_path.push(CssPathSelector::PseudoSelector(ps)),
1275 Err(e) => {
1276 warn_and_continue!(CssParseWarnMsgInner::SkippedRule {
1277 selector: Some(selector),
1278 error: e.into(),
1279 });
1280 }
1281 }
1282 }
1283 Token::Declaration(key, val) => {
1284 if !parser_in_block {
1285 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1286 message: "Declaration outside block"
1287 });
1288 }
1289 current_rules.insert(
1290 key,
1291 (val, (last_error_location, get_error_location(tokenizer))),
1292 );
1293 }
1294 Token::DeclarationStr(content) => {
1295 if !at_rule_stack.is_empty() && !parser_in_block {
1298 let content = content.trim();
1300 if let Ok(nt) = NodeTypeTag::from_str(content) {
1301 last_path.push(CssPathSelector::Type(nt));
1302 } else if content.starts_with('.') {
1303 last_path.push(CssPathSelector::Class(content[1..].to_string().into()));
1304 } else if content.starts_with('#') {
1305 last_path.push(CssPathSelector::Id(content[1..].to_string().into()));
1306 } else if content == "*" {
1307 last_path.push(CssPathSelector::Global);
1308 }
1309 if !last_path.is_empty() {
1311 current_paths.push(last_path.clone());
1312 last_path.clear();
1313 }
1314 }
1315 }
1316 Token::EndOfStream => {
1317 if block_nesting != 0 {
1318 warnings.push(CssParseWarnMsg {
1319 warning: CssParseWarnMsgInner::MalformedStructure {
1320 message: "Unclosed blocks at end of file",
1321 },
1322 location: (last_error_location, get_error_location(tokenizer)),
1323 });
1324 }
1325 break;
1326 }
1327 _ => { }
1328 }
1329
1330 last_error_location = get_error_location(tokenizer);
1331 }
1332
1333 let (stylesheet, mut block_warnings) = css_blocks_to_stylesheet(css_blocks, css_string);
1335 warnings.append(&mut block_warnings);
1336
1337 Ok((stylesheet, warnings))
1338}
1339
1340fn css_blocks_to_stylesheet<'a>(
1341 css_blocks: Vec<UnparsedCssRuleBlock<'a>>,
1342 css_string: &'a str,
1343) -> (Stylesheet, Vec<CssParseWarnMsg<'a>>) {
1344 let css_key_map = crate::props::property::get_css_key_map();
1345 let mut warnings = Vec::new();
1346 let mut parsed_css_blocks = Vec::new();
1347
1348 for unparsed_css_block in css_blocks {
1349 let mut declarations = Vec::<CssDeclaration>::new();
1350
1351 for (unparsed_css_key, (unparsed_css_value, location)) in &unparsed_css_block.declarations {
1352 match parse_declaration_resilient(
1353 unparsed_css_key,
1354 unparsed_css_value,
1355 *location,
1356 &css_key_map,
1357 ) {
1358 Ok(decls) => declarations.extend(decls),
1359 Err(e) => {
1360 warnings.push(CssParseWarnMsg {
1361 warning: CssParseWarnMsgInner::SkippedDeclaration {
1362 key: unparsed_css_key,
1363 value: unparsed_css_value,
1364 error: e,
1365 },
1366 location: *location,
1367 });
1368 }
1369 }
1370 }
1371
1372 parsed_css_blocks.push(CssRuleBlock {
1373 path: unparsed_css_block.path.into(),
1374 declarations: declarations.into(),
1375 conditions: unparsed_css_block.conditions.into(),
1376 });
1377 }
1378
1379 (
1380 Stylesheet {
1381 rules: parsed_css_blocks.into(),
1382 },
1383 warnings,
1384 )
1385}
1386
1387fn parse_declaration_resilient<'a>(
1388 unparsed_css_key: &'a str,
1389 unparsed_css_value: &'a str,
1390 location: (ErrorLocation, ErrorLocation),
1391 css_key_map: &CssKeyMap,
1392) -> Result<Vec<CssDeclaration>, CssParseErrorInner<'a>> {
1393 let mut declarations = Vec::new();
1394
1395 if let Some(combined_key) = CombinedCssPropertyType::from_str(unparsed_css_key, css_key_map) {
1396 if let Some(css_var) = check_if_value_is_css_var(unparsed_css_value) {
1397 return Err(CssParseErrorInner::VarOnShorthandProperty {
1398 key: combined_key,
1399 value: unparsed_css_value,
1400 });
1401 }
1402
1403 match parse_combined_css_property(combined_key, unparsed_css_value) {
1405 Ok(parsed_props) => {
1406 declarations.extend(parsed_props.into_iter().map(CssDeclaration::Static));
1407 }
1408 Err(e) => return Err(CssParseErrorInner::DynamicCssParseError(e.into())),
1409 }
1410 } else if let Some(normal_key) = CssPropertyType::from_str(unparsed_css_key, css_key_map) {
1411 if let Some(css_var) = check_if_value_is_css_var(unparsed_css_value) {
1412 let (css_var_id, css_var_default) = css_var?;
1413 match parse_css_property(normal_key, css_var_default) {
1414 Ok(parsed_default) => {
1415 declarations.push(CssDeclaration::Dynamic(DynamicCssProperty {
1416 dynamic_id: css_var_id.to_string().into(),
1417 default_value: parsed_default,
1418 }));
1419 }
1420 Err(e) => return Err(CssParseErrorInner::DynamicCssParseError(e.into())),
1421 }
1422 } else {
1423 match parse_css_property(normal_key, unparsed_css_value) {
1424 Ok(parsed_value) => {
1425 declarations.push(CssDeclaration::Static(parsed_value));
1426 }
1427 Err(e) => return Err(CssParseErrorInner::DynamicCssParseError(e.into())),
1428 }
1429 }
1430 } else {
1431 return Err(CssParseErrorInner::UnknownPropertyKey(
1432 unparsed_css_key,
1433 unparsed_css_value,
1434 ));
1435 }
1436
1437 Ok(declarations)
1438}
1439
1440fn unparsed_css_blocks_to_stylesheet<'a>(
1441 css_blocks: Vec<UnparsedCssRuleBlock<'a>>,
1442 css_string: &'a str,
1443) -> Result<(Stylesheet, Vec<CssParseWarnMsg<'a>>), CssParseError<'a>> {
1444 let css_key_map = crate::props::property::get_css_key_map();
1446
1447 let mut warnings = Vec::new();
1448
1449 let parsed_css_blocks = css_blocks
1450 .into_iter()
1451 .map(|unparsed_css_block| {
1452 let mut declarations = Vec::<CssDeclaration>::new();
1453
1454 for (unparsed_css_key, (unparsed_css_value, location)) in
1455 unparsed_css_block.declarations
1456 {
1457 parse_css_declaration(
1458 unparsed_css_key,
1459 unparsed_css_value,
1460 location,
1461 &css_key_map,
1462 &mut warnings,
1463 &mut declarations,
1464 )
1465 .map_err(|e| CssParseError {
1466 css_string,
1467 error: e.into(),
1468 location,
1469 })?;
1470 }
1471
1472 Ok(CssRuleBlock {
1473 path: unparsed_css_block.path.into(),
1474 declarations: declarations.into(),
1475 conditions: unparsed_css_block.conditions.into(),
1476 })
1477 })
1478 .collect::<Result<Vec<CssRuleBlock>, CssParseError>>()?;
1479
1480 Ok((parsed_css_blocks.into(), warnings))
1481}
1482
1483pub fn parse_css_declaration<'a>(
1484 unparsed_css_key: &'a str,
1485 unparsed_css_value: &'a str,
1486 location: (ErrorLocation, ErrorLocation),
1487 css_key_map: &CssKeyMap,
1488 warnings: &mut Vec<CssParseWarnMsg<'a>>,
1489 declarations: &mut Vec<CssDeclaration>,
1490) -> Result<(), CssParseErrorInner<'a>> {
1491 match parse_declaration_resilient(unparsed_css_key, unparsed_css_value, location, css_key_map) {
1492 Ok(mut decls) => {
1493 declarations.append(&mut decls);
1494 Ok(())
1495 }
1496 Err(e) => {
1497 if let CssParseErrorInner::UnknownPropertyKey(key, val) = &e {
1498 warnings.push(CssParseWarnMsg {
1499 warning: CssParseWarnMsgInner::UnsupportedKeyValuePair { key, value: val },
1500 location,
1501 });
1502 Ok(()) } else {
1504 Err(e) }
1506 }
1507 }
1508}
1509
1510fn check_if_value_is_css_var<'a>(
1511 unparsed_css_value: &'a str,
1512) -> Option<Result<(&'a str, &'a str), CssParseErrorInner<'a>>> {
1513 const DEFAULT_VARIABLE_DEFAULT: &str = "none";
1514
1515 let (_, brace_contents) = parse_parentheses(unparsed_css_value, &["var"]).ok()?;
1516
1517 Some(match parse_css_variable_brace_contents(brace_contents) {
1519 Some((variable_id, default_value)) => Ok((
1520 variable_id,
1521 default_value.unwrap_or(DEFAULT_VARIABLE_DEFAULT),
1522 )),
1523 None => Err(DynamicCssParseError::InvalidBraceContents(brace_contents).into()),
1524 })
1525}
1526
1527fn parse_css_variable_brace_contents<'a>(input: &'a str) -> Option<(&'a str, Option<&'a str>)> {
1534 let input = input.trim();
1535
1536 let mut split_comma_iter = input.splitn(2, ",");
1537 let var_name = split_comma_iter.next()?;
1538 let var_name = var_name.trim();
1539
1540 if !var_name.starts_with("--") {
1541 return None; }
1543
1544 Some((&var_name[2..], split_comma_iter.next()))
1545}