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, OsCondition,
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 "dragging" => Ok(CssPathPseudoSelector::Dragging),
345 "drag-over" => Ok(CssPathPseudoSelector::DragOver),
346 "nth-child" => {
347 let value = value.ok_or(CssPseudoSelectorParseError::EmptyNthChild)?;
348 let parsed = parse_nth_child_selector(value)?;
349 Ok(CssPathPseudoSelector::NthChild(parsed))
350 }
351 "lang" => {
352 let lang_value = value.ok_or(CssPseudoSelectorParseError::UnknownSelector(
353 selector, value,
354 ))?;
355 let lang_value = lang_value
357 .trim()
358 .trim_start_matches('"')
359 .trim_end_matches('"')
360 .trim_start_matches('\'')
361 .trim_end_matches('\'')
362 .trim();
363 Ok(CssPathPseudoSelector::Lang(AzString::from(
364 lang_value.to_string(),
365 )))
366 }
367 _ => Err(CssPseudoSelectorParseError::UnknownSelector(
368 selector, value,
369 )),
370 }
371}
372
373fn parse_nth_child_selector<'a>(
377 value: &'a str,
378) -> Result<CssNthChildSelector, CssPseudoSelectorParseError<'a>> {
379 let value = value.trim();
380
381 if value.is_empty() {
382 return Err(CssPseudoSelectorParseError::EmptyNthChild);
383 }
384
385 if let Ok(number) = value.parse::<u32>() {
386 return Ok(CssNthChildSelector::Number(number));
387 }
388
389 match value.as_ref() {
391 "even" => Ok(CssNthChildSelector::Even),
392 "odd" => Ok(CssNthChildSelector::Odd),
393 other => parse_nth_child_pattern(value),
394 }
395}
396
397fn parse_nth_child_pattern<'a>(
399 value: &'a str,
400) -> Result<CssNthChildSelector, CssPseudoSelectorParseError<'a>> {
401 use crate::css::CssNthChildPattern;
402
403 let value = value.trim();
404
405 if value.is_empty() {
406 return Err(CssPseudoSelectorParseError::EmptyNthChild);
407 }
408
409 let repeat = value
411 .split("n")
412 .next()
413 .ok_or(CssPseudoSelectorParseError::InvalidNthChildPattern(value))?
414 .trim()
415 .parse::<u32>()?;
416
417 let mut offset_iterator = value.split("+");
419
420 offset_iterator.next().unwrap();
422
423 let offset = match offset_iterator.next() {
424 Some(offset_string) => {
425 let offset_string = offset_string.trim();
426 if offset_string.is_empty() {
427 return Err(CssPseudoSelectorParseError::InvalidNthChildPattern(value));
428 } else {
429 offset_string.parse::<u32>()?
430 }
431 }
432 None => 0,
433 };
434
435 Ok(CssNthChildSelector::Pattern(CssNthChildPattern {
436 pattern_repeat: repeat,
437 offset,
438 }))
439}
440
441#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
442pub struct ErrorLocation {
443 pub original_pos: usize,
444}
445
446impl ErrorLocation {
447 pub fn get_line_column_from_error(&self, css_string: &str) -> (usize, usize) {
449 let error_location = self.original_pos.saturating_sub(1);
450 let (mut line_number, mut total_characters) = (0, 0);
451
452 for line in css_string[0..error_location].lines() {
453 line_number += 1;
454 total_characters += line.chars().count();
455 }
456
457 let total_characters = total_characters + line_number;
459 let column_pos = error_location - total_characters.saturating_sub(2);
460
461 (line_number, column_pos)
462 }
463}
464
465impl<'a> fmt::Display for CssParseError<'a> {
466 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
467 let start_location = self.location.0.get_line_column_from_error(self.css_string);
468 let end_location = self.location.1.get_line_column_from_error(self.css_string);
469 write!(
470 f,
471 " start: line {}:{}\r\n end: line {}:{}\r\n text: \"{}\"\r\n reason: {}",
472 start_location.0,
473 start_location.1,
474 end_location.0,
475 end_location.1,
476 self.get_error_string(),
477 self.error,
478 )
479 }
480}
481
482pub fn new_from_str<'a>(css_string: &'a str) -> (Css, Vec<CssParseWarnMsg<'a>>) {
483 let mut tokenizer = Tokenizer::new(css_string);
484 let (stylesheet, warnings) = match new_from_str_inner(css_string, &mut tokenizer) {
485 Ok((stylesheet, warnings)) => (stylesheet, warnings),
486 Err(error) => {
487 let warning = CssParseWarnMsg {
488 warning: CssParseWarnMsgInner::ParseError(error.error),
489 location: error.location,
490 };
491 (Stylesheet::default(), vec![warning])
492 }
493 };
494
495 (
496 Css {
497 stylesheets: vec![stylesheet].into(),
498 },
499 warnings,
500 )
501}
502
503fn get_error_location(tokenizer: &Tokenizer) -> ErrorLocation {
505 ErrorLocation {
506 original_pos: tokenizer.pos(),
507 }
508}
509
510#[derive(Debug, Clone, PartialEq)]
511pub enum CssPathParseError<'a> {
512 EmptyPath,
513 InvalidTokenEncountered(&'a str),
515 UnexpectedEndOfStream(&'a str),
516 SyntaxError(CssSyntaxError),
517 NodeTypeTag(NodeTypeTagParseError<'a>),
519 PseudoSelectorParseError(CssPseudoSelectorParseError<'a>),
521}
522
523impl_from! { NodeTypeTagParseError<'a>, CssPathParseError::NodeTypeTag }
524impl_from! { CssPseudoSelectorParseError<'a>, CssPathParseError::PseudoSelectorParseError }
525
526impl<'a> From<CssSyntaxError> for CssPathParseError<'a> {
527 fn from(e: CssSyntaxError) -> Self {
528 CssPathParseError::SyntaxError(e)
529 }
530}
531
532#[derive(Debug, Clone, PartialEq)]
533pub enum CssPathParseErrorOwned {
534 EmptyPath,
535 InvalidTokenEncountered(String),
536 UnexpectedEndOfStream(String),
537 SyntaxError(CssSyntaxError),
538 NodeTypeTag(NodeTypeTagParseErrorOwned),
539 PseudoSelectorParseError(CssPseudoSelectorParseErrorOwned),
540}
541
542impl<'a> CssPathParseError<'a> {
543 pub fn to_contained(&self) -> CssPathParseErrorOwned {
544 match self {
545 CssPathParseError::EmptyPath => CssPathParseErrorOwned::EmptyPath,
546 CssPathParseError::InvalidTokenEncountered(s) => {
547 CssPathParseErrorOwned::InvalidTokenEncountered(s.to_string())
548 }
549 CssPathParseError::UnexpectedEndOfStream(s) => {
550 CssPathParseErrorOwned::UnexpectedEndOfStream(s.to_string())
551 }
552 CssPathParseError::SyntaxError(e) => CssPathParseErrorOwned::SyntaxError(e.clone()),
553 CssPathParseError::NodeTypeTag(e) => {
554 CssPathParseErrorOwned::NodeTypeTag(e.to_contained())
555 }
556 CssPathParseError::PseudoSelectorParseError(e) => {
557 CssPathParseErrorOwned::PseudoSelectorParseError(e.to_contained())
558 }
559 }
560 }
561}
562
563impl CssPathParseErrorOwned {
564 pub fn to_shared<'a>(&'a self) -> CssPathParseError<'a> {
565 match self {
566 CssPathParseErrorOwned::EmptyPath => CssPathParseError::EmptyPath,
567 CssPathParseErrorOwned::InvalidTokenEncountered(s) => {
568 CssPathParseError::InvalidTokenEncountered(s)
569 }
570 CssPathParseErrorOwned::UnexpectedEndOfStream(s) => {
571 CssPathParseError::UnexpectedEndOfStream(s)
572 }
573 CssPathParseErrorOwned::SyntaxError(e) => CssPathParseError::SyntaxError(e.clone()),
574 CssPathParseErrorOwned::NodeTypeTag(e) => CssPathParseError::NodeTypeTag(e.to_shared()),
575 CssPathParseErrorOwned::PseudoSelectorParseError(e) => {
576 CssPathParseError::PseudoSelectorParseError(e.to_shared())
577 }
578 }
579 }
580}
581
582pub fn parse_css_path<'a>(input: &'a str) -> Result<CssPath, CssPathParseError<'a>> {
609 use azul_simplecss::{Combinator, Token};
610
611 let input = input.trim();
612 if input.is_empty() {
613 return Err(CssPathParseError::EmptyPath);
614 }
615
616 let mut tokenizer = Tokenizer::new(input);
617 let mut selectors = Vec::new();
618
619 loop {
620 let token = tokenizer.parse_next()?;
621 match token {
622 Token::UniversalSelector => {
623 selectors.push(CssPathSelector::Global);
624 }
625 Token::TypeSelector(div_type) => {
626 if let Ok(nt) = NodeTypeTag::from_str(div_type) {
627 selectors.push(CssPathSelector::Type(nt));
628 }
629 }
630 Token::IdSelector(id) => {
631 selectors.push(CssPathSelector::Id(id.to_string().into()));
632 }
633 Token::ClassSelector(class) => {
634 selectors.push(CssPathSelector::Class(class.to_string().into()));
635 }
636 Token::Combinator(Combinator::GreaterThan) => {
637 selectors.push(CssPathSelector::DirectChildren);
638 }
639 Token::Combinator(Combinator::Space) => {
640 selectors.push(CssPathSelector::Children);
641 }
642 Token::Combinator(Combinator::Plus) => {
643 selectors.push(CssPathSelector::AdjacentSibling);
644 }
645 Token::Combinator(Combinator::Tilde) => {
646 selectors.push(CssPathSelector::GeneralSibling);
647 }
648 Token::PseudoClass { selector, value } => {
649 selectors.push(CssPathSelector::PseudoSelector(pseudo_selector_from_str(
650 selector, value,
651 )?));
652 }
653 Token::EndOfStream => {
654 break;
655 }
656 _ => {
657 return Err(CssPathParseError::InvalidTokenEncountered(input));
658 }
659 }
660 }
661
662 if !selectors.is_empty() {
663 Ok(CssPath {
664 selectors: selectors.into(),
665 })
666 } else {
667 Err(CssPathParseError::EmptyPath)
668 }
669}
670
671#[derive(Debug, Clone, PartialEq)]
672pub struct UnparsedCssRuleBlock<'a> {
673 pub path: CssPath,
675 pub declarations: BTreeMap<&'a str, (&'a str, (ErrorLocation, ErrorLocation))>,
677 pub conditions: Vec<DynamicSelector>,
679}
680
681#[derive(Debug, Clone, PartialEq)]
683pub struct UnparsedCssRuleBlockOwned {
684 pub path: CssPath,
685 pub declarations: BTreeMap<String, (String, (ErrorLocation, ErrorLocation))>,
686 pub conditions: Vec<DynamicSelector>,
687}
688
689impl<'a> UnparsedCssRuleBlock<'a> {
690 pub fn to_contained(&self) -> UnparsedCssRuleBlockOwned {
691 UnparsedCssRuleBlockOwned {
692 path: self.path.clone(),
693 declarations: self
694 .declarations
695 .iter()
696 .map(|(k, (v, loc))| (k.to_string(), (v.to_string(), loc.clone())))
697 .collect(),
698 conditions: self.conditions.clone(),
699 }
700 }
701}
702
703impl UnparsedCssRuleBlockOwned {
704 pub fn to_shared<'a>(&'a self) -> UnparsedCssRuleBlock<'a> {
705 UnparsedCssRuleBlock {
706 path: self.path.clone(),
707 declarations: self
708 .declarations
709 .iter()
710 .map(|(k, (v, loc))| (k.as_str(), (v.as_str(), loc.clone())))
711 .collect(),
712 conditions: self.conditions.clone(),
713 }
714 }
715}
716
717#[derive(Debug, Clone, PartialEq)]
718pub struct CssParseWarnMsg<'a> {
719 pub warning: CssParseWarnMsgInner<'a>,
720 pub location: (ErrorLocation, ErrorLocation),
721}
722
723#[derive(Debug, Clone, PartialEq)]
725pub struct CssParseWarnMsgOwned {
726 pub warning: CssParseWarnMsgInnerOwned,
727 pub location: (ErrorLocation, ErrorLocation),
728}
729
730impl<'a> CssParseWarnMsg<'a> {
731 pub fn to_contained(&self) -> CssParseWarnMsgOwned {
732 CssParseWarnMsgOwned {
733 warning: self.warning.to_contained(),
734 location: self.location.clone(),
735 }
736 }
737}
738
739impl CssParseWarnMsgOwned {
740 pub fn to_shared<'a>(&'a self) -> CssParseWarnMsg<'a> {
741 CssParseWarnMsg {
742 warning: self.warning.to_shared(),
743 location: self.location.clone(),
744 }
745 }
746}
747
748#[derive(Debug, Clone, PartialEq)]
749pub enum CssParseWarnMsgInner<'a> {
750 UnsupportedKeyValuePair { key: &'a str, value: &'a str },
752 ParseError(CssParseErrorInner<'a>),
754 SkippedRule {
756 selector: Option<&'a str>,
757 error: CssParseErrorInner<'a>,
758 },
759 SkippedDeclaration {
761 key: &'a str,
762 value: &'a str,
763 error: CssParseErrorInner<'a>,
764 },
765 MalformedStructure { message: &'a str },
767}
768
769#[derive(Debug, Clone, PartialEq)]
770pub enum CssParseWarnMsgInnerOwned {
771 UnsupportedKeyValuePair {
772 key: String,
773 value: String,
774 },
775 ParseError(CssParseErrorInnerOwned),
776 SkippedRule {
777 selector: Option<String>,
778 error: CssParseErrorInnerOwned,
779 },
780 SkippedDeclaration {
781 key: String,
782 value: String,
783 error: CssParseErrorInnerOwned,
784 },
785 MalformedStructure {
786 message: String,
787 },
788}
789
790impl<'a> CssParseWarnMsgInner<'a> {
791 pub fn to_contained(&self) -> CssParseWarnMsgInnerOwned {
792 match self {
793 Self::UnsupportedKeyValuePair { key, value } => {
794 CssParseWarnMsgInnerOwned::UnsupportedKeyValuePair {
795 key: key.to_string(),
796 value: value.to_string(),
797 }
798 }
799 Self::ParseError(e) => CssParseWarnMsgInnerOwned::ParseError(e.to_contained()),
800 Self::SkippedRule { selector, error } => CssParseWarnMsgInnerOwned::SkippedRule {
801 selector: selector.map(|s| s.to_string()),
802 error: error.to_contained(),
803 },
804 Self::SkippedDeclaration { key, value, error } => {
805 CssParseWarnMsgInnerOwned::SkippedDeclaration {
806 key: key.to_string(),
807 value: value.to_string(),
808 error: error.to_contained(),
809 }
810 }
811 Self::MalformedStructure { message } => CssParseWarnMsgInnerOwned::MalformedStructure {
812 message: message.to_string(),
813 },
814 }
815 }
816}
817
818impl CssParseWarnMsgInnerOwned {
819 pub fn to_shared<'a>(&'a self) -> CssParseWarnMsgInner<'a> {
820 match self {
821 Self::UnsupportedKeyValuePair { key, value } => {
822 CssParseWarnMsgInner::UnsupportedKeyValuePair { key, value }
823 }
824 Self::ParseError(e) => CssParseWarnMsgInner::ParseError(e.to_shared()),
825 Self::SkippedRule { selector, error } => CssParseWarnMsgInner::SkippedRule {
826 selector: selector.as_deref(),
827 error: error.to_shared(),
828 },
829 Self::SkippedDeclaration { key, value, error } => {
830 CssParseWarnMsgInner::SkippedDeclaration {
831 key,
832 value,
833 error: error.to_shared(),
834 }
835 }
836 Self::MalformedStructure { message } => {
837 CssParseWarnMsgInner::MalformedStructure { message }
838 }
839 }
840 }
841}
842
843impl_display! { CssParseWarnMsgInner<'a>, {
844 UnsupportedKeyValuePair { key, value } => format!("Unsupported CSS property: \"{}: {}\"", key, value),
845 ParseError(e) => format!("Parse error (recoverable): {}", e),
846 SkippedRule { selector, error } => {
847 let sel = selector.unwrap_or("unknown");
848 format!("Skipped rule for selector '{}': {}", sel, error)
849 },
850 SkippedDeclaration { key, value, error } => format!("Skipped declaration '{}:{}': {}", key, value, error),
851 MalformedStructure { message } => format!("Malformed CSS structure: {}", message),
852}}
853
854fn parse_media_conditions(content: &str) -> Vec<DynamicSelector> {
857 let mut conditions = Vec::new();
858 let content = content.trim();
859
860 if content.eq_ignore_ascii_case("screen") {
862 conditions.push(DynamicSelector::Media(MediaType::Screen));
863 return conditions;
864 }
865 if content.eq_ignore_ascii_case("print") {
866 conditions.push(DynamicSelector::Media(MediaType::Print));
867 return conditions;
868 }
869 if content.eq_ignore_ascii_case("all") {
870 conditions.push(DynamicSelector::Media(MediaType::All));
871 return conditions;
872 }
873
874 for part in content.split(" and ") {
877 let part = part.trim();
878
879 if part.eq_ignore_ascii_case("screen")
881 || part.eq_ignore_ascii_case("print")
882 || part.eq_ignore_ascii_case("all")
883 {
884 if part.eq_ignore_ascii_case("screen") {
885 conditions.push(DynamicSelector::Media(MediaType::Screen));
886 } else if part.eq_ignore_ascii_case("print") {
887 conditions.push(DynamicSelector::Media(MediaType::Print));
888 }
889 continue;
890 }
891
892 if let Some(inner) = part.strip_prefix('(').and_then(|s| s.strip_suffix(')')) {
894 if let Some(selector) = parse_media_feature(inner) {
895 conditions.push(selector);
896 }
897 }
898 }
899
900 conditions
901}
902
903fn parse_media_feature(feature: &str) -> Option<DynamicSelector> {
905 let parts: Vec<&str> = feature.splitn(2, ':').collect();
906 if parts.len() != 2 {
907 return None;
909 }
910
911 let key = parts[0].trim();
912 let value = parts[1].trim();
913
914 match key.to_lowercase().as_str() {
915 "min-width" => {
916 if let Some(px) = parse_px_value(value) {
917 return Some(DynamicSelector::ViewportWidth(MinMaxRange::new(
918 Some(px),
919 None,
920 )));
921 }
922 }
923 "max-width" => {
924 if let Some(px) = parse_px_value(value) {
925 return Some(DynamicSelector::ViewportWidth(MinMaxRange::new(
926 None,
927 Some(px),
928 )));
929 }
930 }
931 "min-height" => {
932 if let Some(px) = parse_px_value(value) {
933 return Some(DynamicSelector::ViewportHeight(MinMaxRange::new(
934 Some(px),
935 None,
936 )));
937 }
938 }
939 "max-height" => {
940 if let Some(px) = parse_px_value(value) {
941 return Some(DynamicSelector::ViewportHeight(MinMaxRange::new(
942 None,
943 Some(px),
944 )));
945 }
946 }
947 "orientation" => {
948 if value.eq_ignore_ascii_case("portrait") {
949 return Some(DynamicSelector::Orientation(OrientationType::Portrait));
950 } else if value.eq_ignore_ascii_case("landscape") {
951 return Some(DynamicSelector::Orientation(OrientationType::Landscape));
952 }
953 }
954 _ => {}
955 }
956
957 None
958}
959
960fn parse_px_value(value: &str) -> Option<f32> {
962 let value = value.trim();
963 if let Some(num_str) = value.strip_suffix("px") {
964 num_str.trim().parse::<f32>().ok()
965 } else {
966 value.parse::<f32>().ok()
968 }
969}
970
971fn parse_lang_condition(content: &str) -> Option<DynamicSelector> {
974 let content = content.trim();
975
976 let lang = content
978 .strip_prefix('(')
979 .and_then(|s| s.strip_suffix(')'))
980 .unwrap_or(content)
981 .trim();
982
983 let lang = lang
984 .strip_prefix('"')
985 .and_then(|s| s.strip_suffix('"'))
986 .or_else(|| lang.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
987 .unwrap_or(lang)
988 .trim();
989
990 if lang.is_empty() {
991 return None;
992 }
993
994 Some(DynamicSelector::Language(LanguageCondition::Prefix(
996 AzString::from(lang.to_string()),
997 )))
998}
999
1000fn parse_os_condition(content: &str) -> Option<DynamicSelector> {
1003 let content = content.trim();
1004
1005 let os = content
1007 .strip_prefix('(')
1008 .and_then(|s| s.strip_suffix(')'))
1009 .unwrap_or(content)
1010 .trim();
1011
1012 let os = os
1013 .strip_prefix('"')
1014 .and_then(|s| s.strip_suffix('"'))
1015 .or_else(|| os.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
1016 .unwrap_or(os)
1017 .trim();
1018
1019 if os.is_empty() {
1020 return None;
1021 }
1022
1023 let condition = match os.to_lowercase().as_str() {
1025 "linux" => OsCondition::Linux,
1026 "windows" | "win" => OsCondition::Windows,
1027 "macos" | "mac" | "osx" => OsCondition::MacOS,
1028 "ios" => OsCondition::IOS,
1029 "android" => OsCondition::Android,
1030 "apple" => OsCondition::Apple, "web" | "wasm" => OsCondition::Web,
1032 "*" | "any" | "all" => OsCondition::Any,
1033 _ => return None, };
1035
1036 Some(DynamicSelector::Os(condition))
1037}
1038
1039fn new_from_str_inner<'a>(
1045 css_string: &'a str,
1046 tokenizer: &mut Tokenizer<'a>,
1047) -> Result<(Stylesheet, Vec<CssParseWarnMsg<'a>>), CssParseError<'a>> {
1048 use azul_simplecss::{Combinator, Token};
1049
1050 let mut css_blocks = Vec::new();
1051 let mut warnings = Vec::new();
1052
1053 let mut block_nesting = 0_usize;
1054 let mut last_path: Vec<CssPathSelector> = Vec::new();
1055 let mut last_error_location = ErrorLocation { original_pos: 0 };
1056
1057 let mut at_rule_stack: Vec<(Vec<DynamicSelector>, usize)> = Vec::new();
1060 let mut pending_at_rule: Option<&str> = None;
1062 let mut pending_at_str_parts: Vec<String> = Vec::new();
1064
1065 struct NestingLevel<'a> {
1070 paths: Vec<Vec<CssPathSelector>>,
1071 declarations: BTreeMap<&'a str, (&'a str, (ErrorLocation, ErrorLocation))>,
1072 nesting_level: usize,
1073 }
1074 let mut nesting_stack: Vec<NestingLevel<'a>> = Vec::new();
1075 let mut current_paths: Vec<Vec<CssPathSelector>> = Vec::new();
1077 let mut current_declarations: BTreeMap<&str, (&str, (ErrorLocation, ErrorLocation))> = BTreeMap::new();
1079
1080 let max_iterations = css_string.len().saturating_mul(10).max(1000);
1083 let mut iterations = 0_usize;
1084 let mut last_position = 0_usize;
1085 let mut stuck_count = 0_usize;
1086
1087 loop {
1088 iterations += 1;
1090 if iterations > max_iterations {
1091 warnings.push(CssParseWarnMsg {
1092 warning: CssParseWarnMsgInner::MalformedStructure {
1093 message: "Parser iteration limit exceeded - possible infinite loop",
1094 },
1095 location: (last_error_location, get_error_location(tokenizer)),
1096 });
1097 break;
1098 }
1099
1100 let current_position = tokenizer.pos();
1102 if current_position == last_position {
1103 stuck_count += 1;
1104 if stuck_count > 10 {
1105 warnings.push(CssParseWarnMsg {
1106 warning: CssParseWarnMsgInner::MalformedStructure {
1107 message: "Parser stuck - position not advancing",
1108 },
1109 location: (last_error_location, get_error_location(tokenizer)),
1110 });
1111 break;
1112 }
1113 } else {
1114 stuck_count = 0;
1115 last_position = current_position;
1116 }
1117
1118 let token = match tokenizer.parse_next() {
1119 Ok(token) => token,
1120 Err(e) => {
1121 let error_location = get_error_location(tokenizer);
1122 warnings.push(CssParseWarnMsg {
1123 warning: CssParseWarnMsgInner::ParseError(e.into()),
1124 location: (last_error_location, error_location),
1125 });
1126 break;
1128 }
1129 };
1130
1131 macro_rules! warn_and_continue {
1132 ($warning:expr) => {{
1133 warnings.push(CssParseWarnMsg {
1134 warning: $warning,
1135 location: (last_error_location, get_error_location(tokenizer)),
1136 });
1137 continue;
1138 }};
1139 }
1140
1141 fn get_parent_paths(nesting_stack: &[NestingLevel<'_>]) -> Vec<Vec<CssPathSelector>> {
1143 if let Some(parent) = nesting_stack.last() {
1144 parent.paths.clone()
1145 } else {
1146 Vec::new()
1147 }
1148 }
1149
1150 fn combine_paths(
1154 parent_paths: &[Vec<CssPathSelector>],
1155 child_path: &[CssPathSelector],
1156 is_pseudo_only: bool,
1157 ) -> Vec<Vec<CssPathSelector>> {
1158 if parent_paths.is_empty() {
1159 vec![child_path.to_vec()]
1160 } else {
1161 parent_paths
1162 .iter()
1163 .map(|parent| {
1164 let mut combined = parent.clone();
1165 if !is_pseudo_only && !child_path.is_empty() {
1166 combined.push(CssPathSelector::Children);
1168 }
1169 combined.extend(child_path.iter().cloned());
1170 combined
1171 })
1172 .collect()
1173 }
1174 }
1175
1176 match token {
1177 Token::AtRule(rule_name) => {
1178 pending_at_rule = Some(rule_name);
1180 pending_at_str_parts.clear();
1181 }
1182 Token::AtStr(content) => {
1183 if pending_at_rule.is_some() {
1185 if !content.eq_ignore_ascii_case("and") {
1187 pending_at_str_parts.push(content.to_string());
1188 }
1189 }
1190 }
1191 Token::BlockStart => {
1192 if let Some(rule_name) = pending_at_rule.take() {
1194 let combined_content = pending_at_str_parts.join(" and ");
1195 pending_at_str_parts.clear();
1196
1197 let conditions = match rule_name.to_lowercase().as_str() {
1198 "media" => parse_media_conditions(&combined_content),
1199 "lang" => parse_lang_condition(&combined_content).into_iter().collect(),
1200 "os" => parse_os_condition(&combined_content).into_iter().collect(),
1201 _ => {
1202 Vec::new()
1204 }
1205 };
1206
1207 if !conditions.is_empty() {
1208 at_rule_stack.push((conditions, block_nesting + 1));
1210 }
1211 }
1212
1213 block_nesting += 1;
1214
1215 if !current_paths.is_empty() || !last_path.is_empty() {
1217 if !last_path.is_empty() {
1219 current_paths.push(last_path.clone());
1220 last_path.clear();
1221 }
1222
1223 let parent_paths = get_parent_paths(&nesting_stack);
1225 let combined_paths: Vec<Vec<CssPathSelector>> = if parent_paths.is_empty() {
1226 current_paths.clone()
1227 } else {
1228 let mut result = Vec::new();
1230 for parent in &parent_paths {
1231 for child in ¤t_paths {
1232 let is_pseudo_only = child.first().map(|s| matches!(s, CssPathSelector::PseudoSelector(_))).unwrap_or(false);
1234 let mut combined = parent.clone();
1235 if !is_pseudo_only && !child.is_empty() {
1236 combined.push(CssPathSelector::Children);
1237 }
1238 combined.extend(child.iter().cloned());
1239 result.push(combined);
1240 }
1241 }
1242 result
1243 };
1244
1245 nesting_stack.push(NestingLevel {
1247 paths: combined_paths,
1248 declarations: std::mem::take(&mut current_declarations),
1249 nesting_level: block_nesting,
1250 });
1251 current_paths.clear();
1252 }
1253 }
1254 Token::Comma => {
1255 if !last_path.is_empty() {
1257 current_paths.push(last_path.clone());
1258 last_path.clear();
1259 }
1260 }
1261 Token::BlockEnd => {
1262 if block_nesting == 0 {
1263 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1264 message: "Block end without matching block start"
1265 });
1266 }
1267
1268 let current_conditions: Vec<DynamicSelector> = at_rule_stack
1270 .iter()
1271 .flat_map(|(conds, _)| conds.iter().cloned())
1272 .collect();
1273
1274 while let Some((_, level)) = at_rule_stack.last() {
1276 if *level >= block_nesting {
1277 at_rule_stack.pop();
1278 } else {
1279 break;
1280 }
1281 }
1282
1283 block_nesting = block_nesting.saturating_sub(1);
1284
1285 if let Some(level) = nesting_stack.pop() {
1287 if !level.paths.is_empty() && !current_declarations.is_empty() {
1289 css_blocks.extend(level.paths.iter().map(|path| UnparsedCssRuleBlock {
1290 path: CssPath {
1291 selectors: path.clone().into(),
1292 },
1293 declarations: current_declarations.clone(),
1294 conditions: current_conditions.clone(),
1295 }));
1296 }
1297 current_declarations = level.declarations;
1299 }
1300
1301 last_path.clear();
1302 current_paths.clear();
1303 }
1304 Token::UniversalSelector => {
1305 last_path.push(CssPathSelector::Global);
1306 }
1307 Token::TypeSelector(div_type) => {
1308 match NodeTypeTag::from_str(div_type) {
1309 Ok(nt) => last_path.push(CssPathSelector::Type(nt)),
1310 Err(e) => {
1311 warn_and_continue!(CssParseWarnMsgInner::SkippedRule {
1312 selector: Some(div_type),
1313 error: e.into(),
1314 });
1315 }
1316 }
1317 }
1318 Token::IdSelector(id) => {
1319 last_path.push(CssPathSelector::Id(id.to_string().into()));
1320 }
1321 Token::ClassSelector(class) => {
1322 last_path.push(CssPathSelector::Class(class.to_string().into()));
1323 }
1324 Token::Combinator(Combinator::GreaterThan) => {
1325 last_path.push(CssPathSelector::DirectChildren);
1326 }
1327 Token::Combinator(Combinator::Space) => {
1328 last_path.push(CssPathSelector::Children);
1329 }
1330 Token::Combinator(Combinator::Plus) => {
1331 last_path.push(CssPathSelector::AdjacentSibling);
1332 }
1333 Token::Combinator(Combinator::Tilde) => {
1334 last_path.push(CssPathSelector::GeneralSibling);
1335 }
1336 Token::PseudoClass { selector, value } | Token::DoublePseudoClass { selector, value } => {
1337 match pseudo_selector_from_str(selector, value) {
1338 Ok(ps) => last_path.push(CssPathSelector::PseudoSelector(ps)),
1339 Err(e) => {
1340 warn_and_continue!(CssParseWarnMsgInner::SkippedRule {
1341 selector: Some(selector),
1342 error: e.into(),
1343 });
1344 }
1345 }
1346 }
1347 Token::AttributeSelector(attr) => {
1348 last_path.push(CssPathSelector::Class(format!("[{}]", attr).into()));
1351 }
1352 Token::Declaration(key, val) => {
1353 current_declarations.insert(
1354 key,
1355 (val, (last_error_location, get_error_location(tokenizer))),
1356 );
1357 }
1358 Token::EndOfStream => {
1359 if block_nesting != 0 {
1360 warnings.push(CssParseWarnMsg {
1361 warning: CssParseWarnMsgInner::MalformedStructure {
1362 message: "Unclosed blocks at end of file",
1363 },
1364 location: (last_error_location, get_error_location(tokenizer)),
1365 });
1366 }
1367 break;
1368 }
1369 _ => { }
1370 }
1371
1372 last_error_location = get_error_location(tokenizer);
1373 }
1374
1375 let (stylesheet, mut block_warnings) = css_blocks_to_stylesheet(css_blocks, css_string);
1377 warnings.append(&mut block_warnings);
1378
1379 Ok((stylesheet, warnings))
1380}
1381
1382fn css_blocks_to_stylesheet<'a>(
1383 css_blocks: Vec<UnparsedCssRuleBlock<'a>>,
1384 css_string: &'a str,
1385) -> (Stylesheet, Vec<CssParseWarnMsg<'a>>) {
1386 let css_key_map = crate::props::property::get_css_key_map();
1387 let mut warnings = Vec::new();
1388 let mut parsed_css_blocks = Vec::new();
1389
1390 for unparsed_css_block in css_blocks {
1391 let mut declarations = Vec::<CssDeclaration>::new();
1392
1393 for (unparsed_css_key, (unparsed_css_value, location)) in &unparsed_css_block.declarations {
1394 match parse_declaration_resilient(
1395 unparsed_css_key,
1396 unparsed_css_value,
1397 *location,
1398 &css_key_map,
1399 ) {
1400 Ok(decls) => declarations.extend(decls),
1401 Err(e) => {
1402 warnings.push(CssParseWarnMsg {
1403 warning: CssParseWarnMsgInner::SkippedDeclaration {
1404 key: unparsed_css_key,
1405 value: unparsed_css_value,
1406 error: e,
1407 },
1408 location: *location,
1409 });
1410 }
1411 }
1412 }
1413
1414 parsed_css_blocks.push(CssRuleBlock {
1415 path: unparsed_css_block.path.into(),
1416 declarations: declarations.into(),
1417 conditions: unparsed_css_block.conditions.into(),
1418 });
1419 }
1420
1421 (
1422 Stylesheet {
1423 rules: parsed_css_blocks.into(),
1424 },
1425 warnings,
1426 )
1427}
1428
1429fn parse_declaration_resilient<'a>(
1430 unparsed_css_key: &'a str,
1431 unparsed_css_value: &'a str,
1432 location: (ErrorLocation, ErrorLocation),
1433 css_key_map: &CssKeyMap,
1434) -> Result<Vec<CssDeclaration>, CssParseErrorInner<'a>> {
1435 let mut declarations = Vec::new();
1436
1437 if let Some(combined_key) = CombinedCssPropertyType::from_str(unparsed_css_key, css_key_map) {
1438 if let Some(css_var) = check_if_value_is_css_var(unparsed_css_value) {
1439 return Err(CssParseErrorInner::VarOnShorthandProperty {
1440 key: combined_key,
1441 value: unparsed_css_value,
1442 });
1443 }
1444
1445 match parse_combined_css_property(combined_key, unparsed_css_value) {
1447 Ok(parsed_props) => {
1448 declarations.extend(parsed_props.into_iter().map(CssDeclaration::Static));
1449 }
1450 Err(e) => return Err(CssParseErrorInner::DynamicCssParseError(e.into())),
1451 }
1452 } else if let Some(normal_key) = CssPropertyType::from_str(unparsed_css_key, css_key_map) {
1453 if let Some(css_var) = check_if_value_is_css_var(unparsed_css_value) {
1454 let (css_var_id, css_var_default) = css_var?;
1455 match parse_css_property(normal_key, css_var_default) {
1456 Ok(parsed_default) => {
1457 declarations.push(CssDeclaration::Dynamic(DynamicCssProperty {
1458 dynamic_id: css_var_id.to_string().into(),
1459 default_value: parsed_default,
1460 }));
1461 }
1462 Err(e) => return Err(CssParseErrorInner::DynamicCssParseError(e.into())),
1463 }
1464 } else {
1465 match parse_css_property(normal_key, unparsed_css_value) {
1466 Ok(parsed_value) => {
1467 declarations.push(CssDeclaration::Static(parsed_value));
1468 }
1469 Err(e) => return Err(CssParseErrorInner::DynamicCssParseError(e.into())),
1470 }
1471 }
1472 } else {
1473 return Err(CssParseErrorInner::UnknownPropertyKey(
1474 unparsed_css_key,
1475 unparsed_css_value,
1476 ));
1477 }
1478
1479 Ok(declarations)
1480}
1481
1482fn unparsed_css_blocks_to_stylesheet<'a>(
1483 css_blocks: Vec<UnparsedCssRuleBlock<'a>>,
1484 css_string: &'a str,
1485) -> Result<(Stylesheet, Vec<CssParseWarnMsg<'a>>), CssParseError<'a>> {
1486 let css_key_map = crate::props::property::get_css_key_map();
1488
1489 let mut warnings = Vec::new();
1490
1491 let parsed_css_blocks = css_blocks
1492 .into_iter()
1493 .map(|unparsed_css_block| {
1494 let mut declarations = Vec::<CssDeclaration>::new();
1495
1496 for (unparsed_css_key, (unparsed_css_value, location)) in
1497 unparsed_css_block.declarations
1498 {
1499 parse_css_declaration(
1500 unparsed_css_key,
1501 unparsed_css_value,
1502 location,
1503 &css_key_map,
1504 &mut warnings,
1505 &mut declarations,
1506 )
1507 .map_err(|e| CssParseError {
1508 css_string,
1509 error: e.into(),
1510 location,
1511 })?;
1512 }
1513
1514 Ok(CssRuleBlock {
1515 path: unparsed_css_block.path.into(),
1516 declarations: declarations.into(),
1517 conditions: unparsed_css_block.conditions.into(),
1518 })
1519 })
1520 .collect::<Result<Vec<CssRuleBlock>, CssParseError>>()?;
1521
1522 Ok((parsed_css_blocks.into(), warnings))
1523}
1524
1525pub fn parse_css_declaration<'a>(
1526 unparsed_css_key: &'a str,
1527 unparsed_css_value: &'a str,
1528 location: (ErrorLocation, ErrorLocation),
1529 css_key_map: &CssKeyMap,
1530 warnings: &mut Vec<CssParseWarnMsg<'a>>,
1531 declarations: &mut Vec<CssDeclaration>,
1532) -> Result<(), CssParseErrorInner<'a>> {
1533 match parse_declaration_resilient(unparsed_css_key, unparsed_css_value, location, css_key_map) {
1534 Ok(mut decls) => {
1535 declarations.append(&mut decls);
1536 Ok(())
1537 }
1538 Err(e) => {
1539 if let CssParseErrorInner::UnknownPropertyKey(key, val) = &e {
1540 warnings.push(CssParseWarnMsg {
1541 warning: CssParseWarnMsgInner::UnsupportedKeyValuePair { key, value: val },
1542 location,
1543 });
1544 Ok(()) } else {
1546 Err(e) }
1548 }
1549 }
1550}
1551
1552fn check_if_value_is_css_var<'a>(
1553 unparsed_css_value: &'a str,
1554) -> Option<Result<(&'a str, &'a str), CssParseErrorInner<'a>>> {
1555 const DEFAULT_VARIABLE_DEFAULT: &str = "none";
1556
1557 let (_, brace_contents) = parse_parentheses(unparsed_css_value, &["var"]).ok()?;
1558
1559 Some(match parse_css_variable_brace_contents(brace_contents) {
1561 Some((variable_id, default_value)) => Ok((
1562 variable_id,
1563 default_value.unwrap_or(DEFAULT_VARIABLE_DEFAULT),
1564 )),
1565 None => Err(DynamicCssParseError::InvalidBraceContents(brace_contents).into()),
1566 })
1567}
1568
1569fn parse_css_variable_brace_contents<'a>(input: &'a str) -> Option<(&'a str, Option<&'a str>)> {
1576 let input = input.trim();
1577
1578 let mut split_comma_iter = input.splitn(2, ",");
1579 let var_name = split_comma_iter.next()?;
1580 let var_name = var_name.trim();
1581
1582 if !var_name.starts_with("--") {
1583 return None; }
1585
1586 Some((&var_name[2..], split_comma_iter.next()))
1587}