1use alloc::{collections::BTreeMap, string::ToString, vec::Vec};
14use core::{fmt, num::ParseIntError};
15
16pub use azul_simplecss::Error as SimplecssError;
17use azul_simplecss::Tokenizer;
18
19#[derive(Debug, Clone, Copy, PartialEq)]
21#[repr(C)]
22pub struct CssSyntaxErrorPos {
23 pub row: usize,
24 pub col: usize,
25}
26
27impl From<azul_simplecss::ErrorPos> for CssSyntaxErrorPos {
28 fn from(p: azul_simplecss::ErrorPos) -> Self {
29 CssSyntaxErrorPos { row: p.row, col: p.col }
30 }
31}
32
33#[derive(Debug, Clone, Copy, PartialEq)]
35#[repr(C)]
36pub struct CssSyntaxInvalidAdvance {
37 pub expected: isize,
38 pub total: usize,
39 pub pos: CssSyntaxErrorPos,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq)]
44#[repr(C, u8)]
45pub enum CssSyntaxError {
46 UnexpectedEndOfStream(CssSyntaxErrorPos),
47 InvalidAdvance(CssSyntaxInvalidAdvance),
48 UnsupportedToken(CssSyntaxErrorPos),
49 UnknownToken(CssSyntaxErrorPos),
50}
51
52impl From<SimplecssError> for CssSyntaxError {
53 fn from(e: SimplecssError) -> Self {
54 match e {
55 SimplecssError::UnexpectedEndOfStream(pos) => CssSyntaxError::UnexpectedEndOfStream(pos.into()),
56 SimplecssError::InvalidAdvance { expected, total, pos } => CssSyntaxError::InvalidAdvance(CssSyntaxInvalidAdvance { expected, total, pos: pos.into() }),
57 SimplecssError::UnsupportedToken(pos) => CssSyntaxError::UnsupportedToken(pos.into()),
58 SimplecssError::UnknownToken(pos) => CssSyntaxError::UnknownToken(pos.into()),
59 }
60 }
61}
62
63pub use crate::props::property::CssParsingError;
64use crate::{
65 corety::{AzString, OptionString},
66 css::{
67 AttributeMatchOp, Css, CssAttributeSelector, CssDeclaration, CssNthChildSelector, CssPath,
68 CssPathPseudoSelector, CssPathSelector, CssRuleBlock, DynamicCssProperty, NodeTypeTag,
69 NodeTypeTagParseError, NodeTypeTagParseErrorOwned,
70 },
71 dynamic_selector::{
72 BoolCondition, DynamicSelector, DynamicSelectorVec, LanguageCondition, MediaType,
73 MinMaxRange, OrientationType, OsCondition, ThemeCondition, parse_os_version,
74 },
75 props::{
76 basic::parse::parse_parentheses,
77 property::{
78 parse_combined_css_property, parse_css_property, CombinedCssPropertyType, CssKeyMap,
79 CssParsingErrorOwned, CssPropertyType,
80 },
81 },
82};
83
84#[derive(Debug, Clone, PartialEq)]
86pub struct CssParseError<'a> {
87 pub css_string: &'a str,
88 pub error: CssParseErrorInner<'a>,
89 pub location: ErrorLocationRange,
90}
91
92#[derive(Debug, Clone, PartialEq)]
94#[repr(C)]
95pub struct CssParseErrorOwned {
96 pub css_string: AzString,
97 pub error: CssParseErrorInnerOwned,
98 pub location: ErrorLocationRange,
99}
100
101impl<'a> CssParseError<'a> {
102 pub fn to_contained(&self) -> CssParseErrorOwned {
103 CssParseErrorOwned {
104 css_string: self.css_string.to_string().into(),
105 error: self.error.to_contained(),
106 location: self.location,
107 }
108 }
109}
110
111impl CssParseErrorOwned {
112 pub fn to_shared<'a>(&'a self) -> CssParseError<'a> {
113 CssParseError {
114 css_string: self.css_string.as_str(),
115 error: self.error.to_shared(),
116 location: self.location,
117 }
118 }
119}
120
121impl<'a> CssParseError<'a> {
122 pub fn get_error_string(&self) -> &'a str {
124 let (start, end) = (self.location.start.original_pos, self.location.end.original_pos);
125 let s = &self.css_string[start..end];
126 s.trim()
127 }
128}
129
130#[derive(Debug, Clone, PartialEq)]
131pub enum CssParseErrorInner<'a> {
132 ParseError(CssSyntaxError),
134 UnclosedBlock,
136 MalformedCss,
138 DynamicCssParseError(DynamicCssParseError<'a>),
141 PseudoSelectorParseError(CssPseudoSelectorParseError<'a>),
143 NodeTypeTag(NodeTypeTagParseError<'a>),
145 UnknownPropertyKey(&'a str, &'a str),
148 VarOnShorthandProperty {
154 key: CombinedCssPropertyType,
155 value: &'a str,
156 },
157}
158
159#[derive(Debug, Clone, PartialEq)]
161#[repr(C)]
162pub struct UnknownPropertyKeyError {
163 pub key: AzString,
164 pub value: AzString,
165}
166
167#[derive(Debug, Clone, PartialEq)]
169#[repr(C)]
170pub struct VarOnShorthandPropertyError {
171 pub key: CombinedCssPropertyType,
172 pub value: AzString,
173}
174
175#[derive(Debug, Clone, PartialEq)]
176#[repr(C, u8)]
177pub enum CssParseErrorInnerOwned {
178 ParseError(CssSyntaxError),
179 UnclosedBlock,
180 MalformedCss,
181 DynamicCssParseError(DynamicCssParseErrorOwned),
182 PseudoSelectorParseError(CssPseudoSelectorParseErrorOwned),
183 NodeTypeTag(NodeTypeTagParseErrorOwned),
184 UnknownPropertyKey(UnknownPropertyKeyError),
185 VarOnShorthandProperty(VarOnShorthandPropertyError),
186}
187
188impl<'a> CssParseErrorInner<'a> {
189 pub fn to_contained(&self) -> CssParseErrorInnerOwned {
190 match self {
191 CssParseErrorInner::ParseError(e) => CssParseErrorInnerOwned::ParseError(*e),
192 CssParseErrorInner::UnclosedBlock => CssParseErrorInnerOwned::UnclosedBlock,
193 CssParseErrorInner::MalformedCss => CssParseErrorInnerOwned::MalformedCss,
194 CssParseErrorInner::DynamicCssParseError(e) => {
195 CssParseErrorInnerOwned::DynamicCssParseError(e.to_contained())
196 }
197 CssParseErrorInner::PseudoSelectorParseError(e) => {
198 CssParseErrorInnerOwned::PseudoSelectorParseError(e.to_contained())
199 }
200 CssParseErrorInner::NodeTypeTag(e) => {
201 CssParseErrorInnerOwned::NodeTypeTag(e.to_contained())
202 }
203 CssParseErrorInner::UnknownPropertyKey(a, b) => {
204 CssParseErrorInnerOwned::UnknownPropertyKey(UnknownPropertyKeyError { key: a.to_string().into(), value: b.to_string().into() })
205 }
206 CssParseErrorInner::VarOnShorthandProperty { key, value } => {
207 CssParseErrorInnerOwned::VarOnShorthandProperty(VarOnShorthandPropertyError {
208 key: *key,
209 value: value.to_string().into(),
210 })
211 }
212 }
213 }
214}
215
216impl CssParseErrorInnerOwned {
217 pub fn to_shared<'a>(&'a self) -> CssParseErrorInner<'a> {
218 match self {
219 CssParseErrorInnerOwned::ParseError(e) => CssParseErrorInner::ParseError(*e),
220 CssParseErrorInnerOwned::UnclosedBlock => CssParseErrorInner::UnclosedBlock,
221 CssParseErrorInnerOwned::MalformedCss => CssParseErrorInner::MalformedCss,
222 CssParseErrorInnerOwned::DynamicCssParseError(e) => {
223 CssParseErrorInner::DynamicCssParseError(e.to_shared())
224 }
225 CssParseErrorInnerOwned::PseudoSelectorParseError(e) => {
226 CssParseErrorInner::PseudoSelectorParseError(e.to_shared())
227 }
228 CssParseErrorInnerOwned::NodeTypeTag(e) => {
229 CssParseErrorInner::NodeTypeTag(e.to_shared())
230 }
231 CssParseErrorInnerOwned::UnknownPropertyKey(e) => {
232 CssParseErrorInner::UnknownPropertyKey(e.key.as_str(), e.value.as_str())
233 }
234 CssParseErrorInnerOwned::VarOnShorthandProperty(e) => {
235 CssParseErrorInner::VarOnShorthandProperty {
236 key: e.key,
237 value: e.value.as_str(),
238 }
239 }
240 }
241 }
242}
243
244impl_display! { CssParseErrorInner<'a>, {
245 ParseError(e) => format!("Parse Error: {:?}", e),
246 UnclosedBlock => "Unclosed block",
247 MalformedCss => "Malformed Css",
248 DynamicCssParseError(e) => format!("{}", e),
249 PseudoSelectorParseError(e) => format!("Failed to parse pseudo-selector: {}", e),
250 NodeTypeTag(e) => format!("Failed to parse CSS selector path: {}", e),
251 UnknownPropertyKey(k, v) => format!("Unknown CSS key: \"{}: {}\"", k, v),
252 VarOnShorthandProperty { key, value } => format!(
253 "Error while parsing: \"{}: {};\": var() cannot be used on shorthand properties - use `{}-top` or `{}-x` as the key instead: ",
254 key, value, key, key
255 ),
256}}
257
258impl<'a> From<CssSyntaxError> for CssParseErrorInner<'a> {
259 fn from(e: CssSyntaxError) -> Self {
260 CssParseErrorInner::ParseError(e)
261 }
262}
263
264impl<'a> From<SimplecssError> for CssParseErrorInner<'a> {
265 fn from(e: SimplecssError) -> Self {
266 CssParseErrorInner::ParseError(CssSyntaxError::from(e))
267 }
268}
269
270impl_from! { DynamicCssParseError<'a>, CssParseErrorInner::DynamicCssParseError }
271impl_from! { NodeTypeTagParseError<'a>, CssParseErrorInner::NodeTypeTag }
272impl_from! { CssPseudoSelectorParseError<'a>, CssParseErrorInner::PseudoSelectorParseError }
273
274#[derive(Debug, Clone, PartialEq, Eq)]
275pub enum CssPseudoSelectorParseError<'a> {
276 EmptyNthChild,
277 UnknownSelector(&'a str, Option<&'a str>),
278 InvalidNthChildPattern(&'a str),
279 InvalidNthChild(ParseIntError),
280}
281
282impl<'a> From<ParseIntError> for CssPseudoSelectorParseError<'a> {
283 fn from(e: ParseIntError) -> Self {
284 CssPseudoSelectorParseError::InvalidNthChild(e)
285 }
286}
287
288impl_display! { CssPseudoSelectorParseError<'a>, {
289 EmptyNthChild => format!("\
290 Empty :nth-child() selector - nth-child() must at least take a number, \
291 a pattern (such as \"2n+3\") or the values \"even\" or \"odd\"."
292 ),
293 UnknownSelector(selector, value) => {
294 let format_str = match value {
295 Some(v) => format!("{}({})", selector, v),
296 None => selector.to_string(),
297 };
298 format!("Invalid or unknown CSS pseudo-selector: ':{}'", format_str)
299 },
300 InvalidNthChildPattern(selector) => format!(
301 "Invalid pseudo-selector :{} - value has to be a \
302 number, \"even\" or \"odd\" or a pattern such as \"2n+3\"", selector
303 ),
304 InvalidNthChild(e) => format!("Invalid :nth-child pseudo-selector: ':{}'", e),
305}}
306
307#[derive(Debug, Clone, PartialEq)]
309#[repr(C)]
310pub struct UnknownSelectorError {
311 pub selector: AzString,
312 pub suggestion: OptionString,
313}
314
315#[derive(Debug, Clone, PartialEq)]
316#[repr(C, u8)]
317pub enum CssPseudoSelectorParseErrorOwned {
318 EmptyNthChild,
319 UnknownSelector(UnknownSelectorError),
320 InvalidNthChildPattern(AzString),
321 InvalidNthChild(crate::props::basic::error::ParseIntError),
322}
323
324impl<'a> CssPseudoSelectorParseError<'a> {
325 pub fn to_contained(&self) -> CssPseudoSelectorParseErrorOwned {
326 match self {
327 CssPseudoSelectorParseError::EmptyNthChild => {
328 CssPseudoSelectorParseErrorOwned::EmptyNthChild
329 }
330 CssPseudoSelectorParseError::UnknownSelector(a, b) => {
331 CssPseudoSelectorParseErrorOwned::UnknownSelector(UnknownSelectorError {
332 selector: a.to_string().into(),
333 suggestion: b.map(|s| AzString::from(s.to_string())).into(),
334 })
335 }
336 CssPseudoSelectorParseError::InvalidNthChildPattern(s) => {
337 CssPseudoSelectorParseErrorOwned::InvalidNthChildPattern(s.to_string().into())
338 }
339 CssPseudoSelectorParseError::InvalidNthChild(e) => {
340 CssPseudoSelectorParseErrorOwned::InvalidNthChild(e.clone().into())
341 }
342 }
343 }
344}
345
346impl CssPseudoSelectorParseErrorOwned {
347 pub fn to_shared<'a>(&'a self) -> CssPseudoSelectorParseError<'a> {
348 match self {
349 CssPseudoSelectorParseErrorOwned::EmptyNthChild => {
350 CssPseudoSelectorParseError::EmptyNthChild
351 }
352 CssPseudoSelectorParseErrorOwned::UnknownSelector(e) => {
353 CssPseudoSelectorParseError::UnknownSelector(e.selector.as_str(), e.suggestion.as_ref().map(|s| s.as_str()))
354 }
355 CssPseudoSelectorParseErrorOwned::InvalidNthChildPattern(s) => {
356 CssPseudoSelectorParseError::InvalidNthChildPattern(s)
357 }
358 CssPseudoSelectorParseErrorOwned::InvalidNthChild(e) => {
359 CssPseudoSelectorParseError::InvalidNthChild(e.to_std())
360 }
361 }
362 }
363}
364
365#[derive(Debug, Clone, PartialEq)]
367pub enum DynamicCssParseError<'a> {
368 InvalidBraceContents(&'a str),
370 UnexpectedValue(CssParsingError<'a>),
372}
373
374impl_display! { DynamicCssParseError<'a>, {
375 InvalidBraceContents(e) => format!("Invalid contents of var() function: var({})", e),
376 UnexpectedValue(e) => format!("{}", e),
377}}
378
379impl<'a> From<CssParsingError<'a>> for DynamicCssParseError<'a> {
380 fn from(e: CssParsingError<'a>) -> Self {
381 DynamicCssParseError::UnexpectedValue(e)
382 }
383}
384
385#[derive(Debug, Clone, PartialEq)]
386#[repr(C, u8)]
387pub enum DynamicCssParseErrorOwned {
388 InvalidBraceContents(AzString),
389 UnexpectedValue(CssParsingErrorOwned),
390}
391
392impl<'a> DynamicCssParseError<'a> {
393 pub fn to_contained(&self) -> DynamicCssParseErrorOwned {
394 match self {
395 DynamicCssParseError::InvalidBraceContents(s) => {
396 DynamicCssParseErrorOwned::InvalidBraceContents(s.to_string().into())
397 }
398 DynamicCssParseError::UnexpectedValue(e) => {
399 DynamicCssParseErrorOwned::UnexpectedValue(e.to_contained())
400 }
401 }
402 }
403}
404
405impl DynamicCssParseErrorOwned {
406 pub fn to_shared<'a>(&'a self) -> DynamicCssParseError<'a> {
407 match self {
408 DynamicCssParseErrorOwned::InvalidBraceContents(s) => {
409 DynamicCssParseError::InvalidBraceContents(s)
410 }
411 DynamicCssParseErrorOwned::UnexpectedValue(e) => {
412 DynamicCssParseError::UnexpectedValue(e.to_shared())
413 }
414 }
415 }
416}
417
418pub fn pseudo_selector_from_str<'a>(
421 selector: &'a str,
422 value: Option<&'a str>,
423) -> Result<CssPathPseudoSelector, CssPseudoSelectorParseError<'a>> {
424 match selector {
425 "first" => Ok(CssPathPseudoSelector::First),
426 "last" => Ok(CssPathPseudoSelector::Last),
427 "hover" => Ok(CssPathPseudoSelector::Hover),
428 "active" => Ok(CssPathPseudoSelector::Active),
429 "focus" => Ok(CssPathPseudoSelector::Focus),
430 "dragging" => Ok(CssPathPseudoSelector::Dragging),
431 "drag-over" => Ok(CssPathPseudoSelector::DragOver),
432 "nth-child" => {
433 let value = value.ok_or(CssPseudoSelectorParseError::EmptyNthChild)?;
434 let parsed = parse_nth_child_selector(value)?;
435 Ok(CssPathPseudoSelector::NthChild(parsed))
436 }
437 "lang" => {
438 let lang_value = value.ok_or(CssPseudoSelectorParseError::UnknownSelector(
439 selector, value,
440 ))?;
441 let lang_value = lang_value
443 .trim()
444 .trim_start_matches('"')
445 .trim_end_matches('"')
446 .trim_start_matches('\'')
447 .trim_end_matches('\'')
448 .trim();
449 Ok(CssPathPseudoSelector::Lang(AzString::from(
450 lang_value.to_string(),
451 )))
452 }
453 _ => Err(CssPseudoSelectorParseError::UnknownSelector(
454 selector, value,
455 )),
456 }
457}
458
459pub fn parse_attribute_selector(input: &str) -> Option<CssAttributeSelector> {
463 let s = input.trim();
464 if s.is_empty() {
465 return None;
466 }
467
468 let (op, op_pos): (AttributeMatchOp, Option<usize>) = if let Some(i) = s.find("~=") {
470 (AttributeMatchOp::Includes, Some(i))
471 } else if let Some(i) = s.find("|=") {
472 (AttributeMatchOp::DashMatch, Some(i))
473 } else if let Some(i) = s.find("^=") {
474 (AttributeMatchOp::Prefix, Some(i))
475 } else if let Some(i) = s.find("$=") {
476 (AttributeMatchOp::Suffix, Some(i))
477 } else if let Some(i) = s.find("*=") {
478 (AttributeMatchOp::Substring, Some(i))
479 } else if let Some(i) = s.find('=') {
480 (AttributeMatchOp::Eq, Some(i))
481 } else {
482 (AttributeMatchOp::Exists, None)
483 };
484
485 let (name, value) = match op_pos {
486 None => (s, None),
487 Some(i) => {
488 let name = s[..i].trim();
489 let op_len = if matches!(op, AttributeMatchOp::Eq) { 1 } else { 2 };
490 let raw_value = s[i + op_len..].trim();
491 let unquoted = strip_attribute_quotes(raw_value)?;
492 (name, Some(unquoted))
493 }
494 };
495
496 if name.is_empty() {
497 return None;
498 }
499 if name.chars().any(|c| c.is_whitespace() || c == '"' || c == '\'') {
501 return None;
502 }
503
504 Some(CssAttributeSelector {
505 name: name.to_string().into(),
506 op,
507 value: match value {
508 Some(v) => OptionString::Some(v.to_string().into()),
509 None => OptionString::None,
510 },
511 })
512}
513
514fn strip_attribute_quotes(s: &str) -> Option<&str> {
517 let bytes = s.as_bytes();
518 if bytes.len() >= 2 {
519 let first = bytes[0];
520 let last = bytes[bytes.len() - 1];
521 if (first == b'"' && last == b'"') || (first == b'\'' && last == b'\'') {
522 return Some(&s[1..s.len() - 1]);
523 }
524 if first == b'"' || first == b'\'' || last == b'"' || last == b'\'' {
525 return None;
527 }
528 } else if bytes.len() == 1 && (bytes[0] == b'"' || bytes[0] == b'\'') {
529 return None;
530 }
531 Some(s)
532}
533
534fn parse_nth_child_selector<'a>(
538 value: &'a str,
539) -> Result<CssNthChildSelector, CssPseudoSelectorParseError<'a>> {
540 let value = value.trim();
541
542 if value.is_empty() {
543 return Err(CssPseudoSelectorParseError::EmptyNthChild);
544 }
545
546 if let Ok(number) = value.parse::<u32>() {
547 return Ok(CssNthChildSelector::Number(number));
548 }
549
550 match value {
552 "even" => Ok(CssNthChildSelector::Even),
553 "odd" => Ok(CssNthChildSelector::Odd),
554 _ => parse_nth_child_pattern(value),
555 }
556}
557
558fn parse_nth_child_pattern<'a>(
560 value: &'a str,
561) -> Result<CssNthChildSelector, CssPseudoSelectorParseError<'a>> {
562 use crate::css::CssNthChildPattern;
563
564 let value = value.trim();
565
566 if value.is_empty() {
567 return Err(CssPseudoSelectorParseError::EmptyNthChild);
568 }
569
570 let repeat = value
572 .split("n")
573 .next()
574 .ok_or(CssPseudoSelectorParseError::InvalidNthChildPattern(value))?
575 .trim()
576 .parse::<u32>()?;
577
578 let mut offset_iterator = value.split("+");
580
581 offset_iterator.next().unwrap();
583
584 let offset = match offset_iterator.next() {
585 Some(offset_string) => {
586 let offset_string = offset_string.trim();
587 if offset_string.is_empty() {
588 return Err(CssPseudoSelectorParseError::InvalidNthChildPattern(value));
589 } else {
590 offset_string.parse::<u32>()?
591 }
592 }
593 None => 0,
594 };
595
596 Ok(CssNthChildSelector::Pattern(CssNthChildPattern {
597 pattern_repeat: repeat,
598 offset,
599 }))
600}
601
602#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
603#[repr(C)]
604pub struct ErrorLocation {
605 pub original_pos: usize,
606}
607
608#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
611#[repr(C)]
612pub struct ErrorLocationRange {
613 pub start: ErrorLocation,
614 pub end: ErrorLocation,
615}
616
617impl ErrorLocation {
618 pub fn get_line_column_from_error(&self, css_string: &str) -> (usize, usize) {
620 let error_location = self.original_pos.saturating_sub(1);
621 let (mut line_number, mut total_characters) = (0, 0);
622
623 for line in css_string[0..error_location].lines() {
624 line_number += 1;
625 total_characters += line.chars().count();
626 }
627
628 let total_characters = total_characters + line_number;
630 let column_pos = error_location - total_characters.saturating_sub(2);
631
632 (line_number, column_pos)
633 }
634}
635
636impl<'a> fmt::Display for CssParseError<'a> {
637 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
638 let start_location = self.location.start.get_line_column_from_error(self.css_string);
639 let end_location = self.location.end.get_line_column_from_error(self.css_string);
640 write!(
641 f,
642 " start: line {}:{}\r\n end: line {}:{}\r\n text: \"{}\"\r\n reason: {}",
643 start_location.0,
644 start_location.1,
645 end_location.0,
646 end_location.1,
647 self.get_error_string(),
648 self.error,
649 )
650 }
651}
652
653pub fn new_from_str<'a>(css_string: &'a str) -> (Css, Vec<CssParseWarnMsg<'a>>) {
659 let mut tokenizer = Tokenizer::new(css_string);
660 let (rules, warnings) = match new_from_str_inner(css_string, &mut tokenizer) {
661 Ok((rules, warnings)) => (rules, warnings),
662 Err(error) => {
663 let warning = CssParseWarnMsg {
664 warning: CssParseWarnMsgInner::ParseError(error.error),
665 location: error.location,
666 };
667 (Vec::<CssRuleBlock>::new(), vec![warning])
668 }
669 };
670
671 (
672 Css { rules: rules.into() },
673 warnings,
674 )
675}
676
677fn get_error_location(tokenizer: &Tokenizer) -> ErrorLocation {
679 ErrorLocation {
680 original_pos: tokenizer.pos(),
681 }
682}
683
684#[derive(Debug, Clone, PartialEq)]
685pub enum CssPathParseError<'a> {
686 EmptyPath,
687 InvalidTokenEncountered(&'a str),
689 UnexpectedEndOfStream(&'a str),
690 SyntaxError(CssSyntaxError),
691 NodeTypeTag(NodeTypeTagParseError<'a>),
693 PseudoSelectorParseError(CssPseudoSelectorParseError<'a>),
695}
696
697impl_from! { NodeTypeTagParseError<'a>, CssPathParseError::NodeTypeTag }
698impl_from! { CssPseudoSelectorParseError<'a>, CssPathParseError::PseudoSelectorParseError }
699
700impl<'a> From<CssSyntaxError> for CssPathParseError<'a> {
701 fn from(e: CssSyntaxError) -> Self {
702 CssPathParseError::SyntaxError(e)
703 }
704}
705
706impl<'a> From<SimplecssError> for CssPathParseError<'a> {
707 fn from(e: SimplecssError) -> Self {
708 CssPathParseError::SyntaxError(CssSyntaxError::from(e))
709 }
710}
711
712#[derive(Debug, Clone, PartialEq)]
713pub enum CssPathParseErrorOwned {
714 EmptyPath,
715 InvalidTokenEncountered(AzString),
716 UnexpectedEndOfStream(AzString),
717 SyntaxError(CssSyntaxError),
718 NodeTypeTag(NodeTypeTagParseErrorOwned),
719 PseudoSelectorParseError(CssPseudoSelectorParseErrorOwned),
720}
721
722impl<'a> CssPathParseError<'a> {
723 pub fn to_contained(&self) -> CssPathParseErrorOwned {
724 match self {
725 CssPathParseError::EmptyPath => CssPathParseErrorOwned::EmptyPath,
726 CssPathParseError::InvalidTokenEncountered(s) => {
727 CssPathParseErrorOwned::InvalidTokenEncountered(s.to_string().into())
728 }
729 CssPathParseError::UnexpectedEndOfStream(s) => {
730 CssPathParseErrorOwned::UnexpectedEndOfStream(s.to_string().into())
731 }
732 CssPathParseError::SyntaxError(e) => CssPathParseErrorOwned::SyntaxError(*e),
733 CssPathParseError::NodeTypeTag(e) => {
734 CssPathParseErrorOwned::NodeTypeTag(e.to_contained())
735 }
736 CssPathParseError::PseudoSelectorParseError(e) => {
737 CssPathParseErrorOwned::PseudoSelectorParseError(e.to_contained())
738 }
739 }
740 }
741}
742
743impl CssPathParseErrorOwned {
744 pub fn to_shared<'a>(&'a self) -> CssPathParseError<'a> {
745 match self {
746 CssPathParseErrorOwned::EmptyPath => CssPathParseError::EmptyPath,
747 CssPathParseErrorOwned::InvalidTokenEncountered(s) => {
748 CssPathParseError::InvalidTokenEncountered(s)
749 }
750 CssPathParseErrorOwned::UnexpectedEndOfStream(s) => {
751 CssPathParseError::UnexpectedEndOfStream(s)
752 }
753 CssPathParseErrorOwned::SyntaxError(e) => CssPathParseError::SyntaxError(*e),
754 CssPathParseErrorOwned::NodeTypeTag(e) => CssPathParseError::NodeTypeTag(e.to_shared()),
755 CssPathParseErrorOwned::PseudoSelectorParseError(e) => {
756 CssPathParseError::PseudoSelectorParseError(e.to_shared())
757 }
758 }
759 }
760}
761
762pub fn parse_css_path<'a>(input: &'a str) -> Result<CssPath, CssPathParseError<'a>> {
789 use azul_simplecss::{Combinator, Token};
790
791 let input = input.trim();
792 if input.is_empty() {
793 return Err(CssPathParseError::EmptyPath);
794 }
795
796 let mut tokenizer = Tokenizer::new(input);
797 let mut selectors = Vec::new();
798
799 loop {
800 let token = tokenizer.parse_next()?;
801 match token {
802 Token::UniversalSelector => {
803 selectors.push(CssPathSelector::Global);
804 }
805 Token::TypeSelector(div_type) => {
806 if let Ok(nt) = NodeTypeTag::from_str(div_type) {
807 selectors.push(CssPathSelector::Type(nt));
808 }
809 }
810 Token::IdSelector(id) => {
811 selectors.push(CssPathSelector::Id(id.to_string().into()));
812 }
813 Token::ClassSelector(class) => {
814 selectors.push(CssPathSelector::Class(class.to_string().into()));
815 }
816 Token::Combinator(Combinator::GreaterThan) => {
817 selectors.push(CssPathSelector::DirectChildren);
818 }
819 Token::Combinator(Combinator::Space) => {
820 selectors.push(CssPathSelector::Children);
821 }
822 Token::Combinator(Combinator::Plus) => {
823 selectors.push(CssPathSelector::AdjacentSibling);
824 }
825 Token::Combinator(Combinator::Tilde) => {
826 selectors.push(CssPathSelector::GeneralSibling);
827 }
828 Token::PseudoClass { selector, value } => {
829 selectors.push(CssPathSelector::PseudoSelector(pseudo_selector_from_str(
830 selector, value,
831 )?));
832 }
833 Token::EndOfStream => {
834 break;
835 }
836 _ => {
837 return Err(CssPathParseError::InvalidTokenEncountered(input));
838 }
839 }
840 }
841
842 if !selectors.is_empty() {
843 Ok(CssPath {
844 selectors: selectors.into(),
845 })
846 } else {
847 Err(CssPathParseError::EmptyPath)
848 }
849}
850
851#[derive(Debug, Clone, PartialEq)]
852pub struct UnparsedCssRuleBlock<'a> {
853 pub path: CssPath,
855 pub declarations: BTreeMap<&'a str, (&'a str, ErrorLocationRange)>,
857 pub conditions: Vec<DynamicSelector>,
859}
860
861#[derive(Debug, Clone, PartialEq)]
863pub struct UnparsedCssRuleBlockOwned {
864 pub path: CssPath,
865 pub declarations: BTreeMap<String, (String, ErrorLocationRange)>,
866 pub conditions: Vec<DynamicSelector>,
867}
868
869impl<'a> UnparsedCssRuleBlock<'a> {
870 pub fn to_contained(&self) -> UnparsedCssRuleBlockOwned {
871 UnparsedCssRuleBlockOwned {
872 path: self.path.clone(),
873 declarations: self
874 .declarations
875 .iter()
876 .map(|(k, (v, loc))| (k.to_string(), (v.to_string(), *loc)))
877 .collect(),
878 conditions: self.conditions.clone(),
879 }
880 }
881}
882
883impl UnparsedCssRuleBlockOwned {
884 pub fn to_shared<'a>(&'a self) -> UnparsedCssRuleBlock<'a> {
885 UnparsedCssRuleBlock {
886 path: self.path.clone(),
887 declarations: self
888 .declarations
889 .iter()
890 .map(|(k, (v, loc))| (k.as_str(), (v.as_str(), *loc)))
891 .collect(),
892 conditions: self.conditions.clone(),
893 }
894 }
895}
896
897#[derive(Debug, Clone, PartialEq)]
898pub struct CssParseWarnMsg<'a> {
899 pub warning: CssParseWarnMsgInner<'a>,
900 pub location: ErrorLocationRange,
901}
902
903#[derive(Debug, Clone, PartialEq)]
905pub struct CssParseWarnMsgOwned {
906 pub warning: CssParseWarnMsgInnerOwned,
907 pub location: ErrorLocationRange,
908}
909
910impl<'a> CssParseWarnMsg<'a> {
911 pub fn to_contained(&self) -> CssParseWarnMsgOwned {
912 CssParseWarnMsgOwned {
913 warning: self.warning.to_contained(),
914 location: self.location,
915 }
916 }
917}
918
919impl CssParseWarnMsgOwned {
920 pub fn to_shared<'a>(&'a self) -> CssParseWarnMsg<'a> {
921 CssParseWarnMsg {
922 warning: self.warning.to_shared(),
923 location: self.location,
924 }
925 }
926}
927
928#[derive(Debug, Clone, PartialEq)]
929pub enum CssParseWarnMsgInner<'a> {
930 UnsupportedKeyValuePair { key: &'a str, value: &'a str },
932 ParseError(CssParseErrorInner<'a>),
934 SkippedRule {
936 selector: Option<&'a str>,
937 error: CssParseErrorInner<'a>,
938 },
939 SkippedDeclaration {
941 key: &'a str,
942 value: &'a str,
943 error: CssParseErrorInner<'a>,
944 },
945 MalformedStructure { message: &'a str },
947}
948
949#[derive(Debug, Clone, PartialEq)]
950pub enum CssParseWarnMsgInnerOwned {
951 UnsupportedKeyValuePair {
952 key: String,
953 value: String,
954 },
955 ParseError(CssParseErrorInnerOwned),
956 SkippedRule {
957 selector: Option<String>,
958 error: CssParseErrorInnerOwned,
959 },
960 SkippedDeclaration {
961 key: String,
962 value: String,
963 error: CssParseErrorInnerOwned,
964 },
965 MalformedStructure {
966 message: String,
967 },
968}
969
970impl<'a> CssParseWarnMsgInner<'a> {
971 pub fn to_contained(&self) -> CssParseWarnMsgInnerOwned {
972 match self {
973 Self::UnsupportedKeyValuePair { key, value } => {
974 CssParseWarnMsgInnerOwned::UnsupportedKeyValuePair {
975 key: key.to_string(),
976 value: value.to_string(),
977 }
978 }
979 Self::ParseError(e) => CssParseWarnMsgInnerOwned::ParseError(e.to_contained()),
980 Self::SkippedRule { selector, error } => CssParseWarnMsgInnerOwned::SkippedRule {
981 selector: selector.map(|s| s.to_string()),
982 error: error.to_contained(),
983 },
984 Self::SkippedDeclaration { key, value, error } => {
985 CssParseWarnMsgInnerOwned::SkippedDeclaration {
986 key: key.to_string(),
987 value: value.to_string(),
988 error: error.to_contained(),
989 }
990 }
991 Self::MalformedStructure { message } => CssParseWarnMsgInnerOwned::MalformedStructure {
992 message: message.to_string(),
993 },
994 }
995 }
996}
997
998impl CssParseWarnMsgInnerOwned {
999 pub fn to_shared<'a>(&'a self) -> CssParseWarnMsgInner<'a> {
1000 match self {
1001 Self::UnsupportedKeyValuePair { key, value } => {
1002 CssParseWarnMsgInner::UnsupportedKeyValuePair { key, value }
1003 }
1004 Self::ParseError(e) => CssParseWarnMsgInner::ParseError(e.to_shared()),
1005 Self::SkippedRule { selector, error } => CssParseWarnMsgInner::SkippedRule {
1006 selector: selector.as_deref(),
1007 error: error.to_shared(),
1008 },
1009 Self::SkippedDeclaration { key, value, error } => {
1010 CssParseWarnMsgInner::SkippedDeclaration {
1011 key,
1012 value,
1013 error: error.to_shared(),
1014 }
1015 }
1016 Self::MalformedStructure { message } => {
1017 CssParseWarnMsgInner::MalformedStructure { message }
1018 }
1019 }
1020 }
1021}
1022
1023impl_display! { CssParseWarnMsgInner<'a>, {
1024 UnsupportedKeyValuePair { key, value } => format!("Unsupported CSS property: \"{}: {}\"", key, value),
1025 ParseError(e) => format!("Parse error (recoverable): {}", e),
1026 SkippedRule { selector, error } => {
1027 let sel = selector.unwrap_or("unknown");
1028 format!("Skipped rule for selector '{}': {}", sel, error)
1029 },
1030 SkippedDeclaration { key, value, error } => format!("Skipped declaration '{}:{}': {}", key, value, error),
1031 MalformedStructure { message } => format!("Malformed CSS structure: {}", message),
1032}}
1033
1034fn parse_media_conditions(content: &str) -> Vec<DynamicSelector> {
1037 let mut conditions = Vec::new();
1038 let content = content.trim();
1039
1040 if content.eq_ignore_ascii_case("screen") {
1042 conditions.push(DynamicSelector::Media(MediaType::Screen));
1043 return conditions;
1044 }
1045 if content.eq_ignore_ascii_case("print") {
1046 conditions.push(DynamicSelector::Media(MediaType::Print));
1047 return conditions;
1048 }
1049 if content.eq_ignore_ascii_case("all") {
1050 conditions.push(DynamicSelector::Media(MediaType::All));
1051 return conditions;
1052 }
1053
1054 for part in content.split(" and ") {
1057 let part = part.trim();
1058
1059 if part.eq_ignore_ascii_case("screen")
1061 || part.eq_ignore_ascii_case("print")
1062 || part.eq_ignore_ascii_case("all")
1063 {
1064 if part.eq_ignore_ascii_case("screen") {
1065 conditions.push(DynamicSelector::Media(MediaType::Screen));
1066 } else if part.eq_ignore_ascii_case("print") {
1067 conditions.push(DynamicSelector::Media(MediaType::Print));
1068 } else if part.eq_ignore_ascii_case("all") {
1069 conditions.push(DynamicSelector::Media(MediaType::All));
1070 }
1071 continue;
1072 }
1073
1074 if let Some(inner) = part.strip_prefix('(').and_then(|s| s.strip_suffix(')')) {
1076 if let Some(selector) = parse_media_feature(inner) {
1077 conditions.push(selector);
1078 }
1079 }
1080 }
1081
1082 conditions
1083}
1084
1085fn parse_media_feature(feature: &str) -> Option<DynamicSelector> {
1087 let parts: Vec<&str> = feature.splitn(2, ':').collect();
1088 if parts.len() != 2 {
1089 return None;
1091 }
1092
1093 let key = parts[0].trim();
1094 let value = parts[1].trim();
1095
1096 match key.to_lowercase().as_str() {
1097 "min-width" => {
1098 if let Some(px) = parse_px_value(value) {
1099 return Some(DynamicSelector::ViewportWidth(MinMaxRange::new(
1100 Some(px),
1101 None,
1102 )));
1103 }
1104 }
1105 "max-width" => {
1106 if let Some(px) = parse_px_value(value) {
1107 return Some(DynamicSelector::ViewportWidth(MinMaxRange::new(
1108 None,
1109 Some(px),
1110 )));
1111 }
1112 }
1113 "min-height" => {
1114 if let Some(px) = parse_px_value(value) {
1115 return Some(DynamicSelector::ViewportHeight(MinMaxRange::new(
1116 Some(px),
1117 None,
1118 )));
1119 }
1120 }
1121 "max-height" => {
1122 if let Some(px) = parse_px_value(value) {
1123 return Some(DynamicSelector::ViewportHeight(MinMaxRange::new(
1124 None,
1125 Some(px),
1126 )));
1127 }
1128 }
1129 "orientation" => {
1130 if value.eq_ignore_ascii_case("portrait") {
1131 return Some(DynamicSelector::Orientation(OrientationType::Portrait));
1132 } else if value.eq_ignore_ascii_case("landscape") {
1133 return Some(DynamicSelector::Orientation(OrientationType::Landscape));
1134 }
1135 }
1136 "prefers-color-scheme" => {
1137 if value.eq_ignore_ascii_case("dark") {
1138 return Some(DynamicSelector::Theme(ThemeCondition::Dark));
1139 } else if value.eq_ignore_ascii_case("light") {
1140 return Some(DynamicSelector::Theme(ThemeCondition::Light));
1141 }
1142 }
1143 "prefers-reduced-motion" => {
1144 if value.eq_ignore_ascii_case("reduce") {
1145 return Some(DynamicSelector::PrefersReducedMotion(BoolCondition::True));
1146 } else if value.eq_ignore_ascii_case("no-preference") {
1147 return Some(DynamicSelector::PrefersReducedMotion(BoolCondition::False));
1148 }
1149 }
1150 "prefers-contrast" | "prefers-high-contrast" => {
1151 if value.eq_ignore_ascii_case("more") || value.eq_ignore_ascii_case("high") || value.eq_ignore_ascii_case("active") {
1152 return Some(DynamicSelector::PrefersHighContrast(BoolCondition::True));
1153 } else if value.eq_ignore_ascii_case("no-preference") || value.eq_ignore_ascii_case("none") {
1154 return Some(DynamicSelector::PrefersHighContrast(BoolCondition::False));
1155 }
1156 }
1157 "aspect-ratio" => {
1158 if let Some(ratio) = parse_ratio_value(value) {
1159 return Some(DynamicSelector::AspectRatio(MinMaxRange::new(Some(ratio), Some(ratio))));
1160 }
1161 }
1162 "min-aspect-ratio" => {
1163 if let Some(ratio) = parse_ratio_value(value) {
1164 return Some(DynamicSelector::AspectRatio(MinMaxRange::new(Some(ratio), None)));
1165 }
1166 }
1167 "max-aspect-ratio" => {
1168 if let Some(ratio) = parse_ratio_value(value) {
1169 return Some(DynamicSelector::AspectRatio(MinMaxRange::new(None, Some(ratio))));
1170 }
1171 }
1172 _ => {}
1173 }
1174
1175 None
1176}
1177
1178fn parse_px_value(value: &str) -> Option<f32> {
1180 let value = value.trim();
1181 if let Some(num_str) = value.strip_suffix("px") {
1182 num_str.trim().parse::<f32>().ok()
1183 } else {
1184 value.parse::<f32>().ok()
1186 }
1187}
1188
1189fn parse_ratio_value(value: &str) -> Option<f32> {
1191 let value = value.trim();
1192 if let Some((num, den)) = value.split_once('/') {
1193 let num: f32 = num.trim().parse().ok()?;
1194 let den: f32 = den.trim().parse().ok()?;
1195 if den == 0.0 { return None; }
1196 Some(num / den)
1197 } else {
1198 value.parse::<f32>().ok()
1199 }
1200}
1201
1202fn parse_container_conditions(content: &str) -> Vec<DynamicSelector> {
1205 let mut conditions = Vec::new();
1206 let content = content.trim();
1207
1208 let (name_part, query_part) = if content.starts_with('(') {
1211 (None, content)
1212 } else if let Some(paren_idx) = content.find('(') {
1213 let name = content[..paren_idx].trim();
1214 if !name.is_empty() {
1215 (Some(name), &content[paren_idx..])
1216 } else {
1217 (None, content)
1218 }
1219 } else {
1220 if !content.is_empty() {
1222 conditions.push(DynamicSelector::ContainerName(AzString::from(content.to_string())));
1223 }
1224 return conditions;
1225 };
1226
1227 if let Some(name) = name_part {
1228 conditions.push(DynamicSelector::ContainerName(AzString::from(name.to_string())));
1229 }
1230
1231 for part in query_part.split(" and ") {
1233 let part = part.trim();
1234 if let Some(inner) = part.strip_prefix('(').and_then(|s| s.strip_suffix(')')) {
1235 if let Some(selector) = parse_container_feature(inner) {
1236 conditions.push(selector);
1237 }
1238 }
1239 }
1240
1241 conditions
1242}
1243
1244fn parse_container_feature(feature: &str) -> Option<DynamicSelector> {
1246 let (key, value) = feature.split_once(':')?;
1247 let key = key.trim();
1248 let value = value.trim();
1249
1250 match key.to_lowercase().as_str() {
1251 "min-width" => {
1252 parse_px_value(value).map(|px| DynamicSelector::ContainerWidth(MinMaxRange::new(Some(px), None)))
1253 }
1254 "max-width" => {
1255 parse_px_value(value).map(|px| DynamicSelector::ContainerWidth(MinMaxRange::new(None, Some(px))))
1256 }
1257 "min-height" => {
1258 parse_px_value(value).map(|px| DynamicSelector::ContainerHeight(MinMaxRange::new(Some(px), None)))
1259 }
1260 "max-height" => {
1261 parse_px_value(value).map(|px| DynamicSelector::ContainerHeight(MinMaxRange::new(None, Some(px))))
1262 }
1263 "aspect-ratio" => {
1264 parse_ratio_value(value).map(|r| DynamicSelector::AspectRatio(MinMaxRange::new(Some(r), Some(r))))
1265 }
1266 "min-aspect-ratio" => {
1267 parse_ratio_value(value).map(|r| DynamicSelector::AspectRatio(MinMaxRange::new(Some(r), None)))
1268 }
1269 "max-aspect-ratio" => {
1270 parse_ratio_value(value).map(|r| DynamicSelector::AspectRatio(MinMaxRange::new(None, Some(r))))
1271 }
1272 _ => None,
1273 }
1274}
1275
1276fn parse_theme_condition(content: &str) -> Option<DynamicSelector> {
1279 let content = content.trim();
1280 let inner = content
1281 .strip_prefix('(')
1282 .and_then(|s| s.strip_suffix(')'))
1283 .unwrap_or(content)
1284 .trim();
1285 let inner = inner
1286 .strip_prefix('"')
1287 .and_then(|s| s.strip_suffix('"'))
1288 .or_else(|| inner.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
1289 .unwrap_or(inner)
1290 .trim();
1291
1292 match inner.to_lowercase().as_str() {
1293 "dark" => Some(DynamicSelector::Theme(ThemeCondition::Dark)),
1294 "light" => Some(DynamicSelector::Theme(ThemeCondition::Light)),
1295 _ => None,
1296 }
1297}
1298
1299fn parse_lang_condition(content: &str) -> Option<DynamicSelector> {
1302 let content = content.trim();
1303
1304 let lang = content
1306 .strip_prefix('(')
1307 .and_then(|s| s.strip_suffix(')'))
1308 .unwrap_or(content)
1309 .trim();
1310
1311 let lang = lang
1312 .strip_prefix('"')
1313 .and_then(|s| s.strip_suffix('"'))
1314 .or_else(|| lang.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')))
1315 .unwrap_or(lang)
1316 .trim();
1317
1318 if lang.is_empty() {
1319 return None;
1320 }
1321
1322 Some(DynamicSelector::Language(LanguageCondition::Prefix(
1324 AzString::from(lang.to_string()),
1325 )))
1326}
1327
1328fn new_from_str_inner<'a>(
1334 css_string: &'a str,
1335 tokenizer: &mut Tokenizer<'a>,
1336) -> Result<(Vec<CssRuleBlock>, Vec<CssParseWarnMsg<'a>>), CssParseError<'a>> {
1337 use azul_simplecss::{Combinator, Token};
1338
1339 let mut css_blocks = Vec::new();
1340 let mut warnings = Vec::new();
1341
1342 let mut block_nesting = 0_usize;
1343 let mut last_path: Vec<CssPathSelector> = Vec::new();
1344 let mut last_error_location = ErrorLocation { original_pos: 0 };
1345
1346 let mut at_rule_stack: Vec<(Vec<DynamicSelector>, usize)> = Vec::new();
1349 let mut pending_at_rule: Option<&str> = None;
1351 let mut pending_at_str_parts: Vec<String> = Vec::new();
1353
1354 struct NestingLevel<'a> {
1359 paths: Vec<Vec<CssPathSelector>>,
1360 declarations: BTreeMap<&'a str, (&'a str, ErrorLocationRange)>,
1361 nesting_level: usize,
1362 }
1363 let mut nesting_stack: Vec<NestingLevel<'a>> = Vec::new();
1364 let mut current_paths: Vec<Vec<CssPathSelector>> = Vec::new();
1366 let mut current_declarations: BTreeMap<&str, (&str, ErrorLocationRange)> = BTreeMap::new();
1368
1369 let max_iterations = css_string.len().saturating_mul(10).max(1000);
1372 let mut iterations = 0_usize;
1373 let mut last_position = 0_usize;
1374 let mut stuck_count = 0_usize;
1375
1376 loop {
1377 iterations += 1;
1379 if iterations > max_iterations {
1380 warnings.push(CssParseWarnMsg {
1381 warning: CssParseWarnMsgInner::MalformedStructure {
1382 message: "Parser iteration limit exceeded - possible infinite loop",
1383 },
1384 location: ErrorLocationRange { start: last_error_location, end: get_error_location(tokenizer) },
1385 });
1386 break;
1387 }
1388
1389 let current_position = tokenizer.pos();
1391 if current_position == last_position {
1392 stuck_count += 1;
1393 if stuck_count > 10 {
1394 warnings.push(CssParseWarnMsg {
1395 warning: CssParseWarnMsgInner::MalformedStructure {
1396 message: "Parser stuck - position not advancing",
1397 },
1398 location: ErrorLocationRange { start: last_error_location, end: get_error_location(tokenizer) },
1399 });
1400 break;
1401 }
1402 } else {
1403 stuck_count = 0;
1404 last_position = current_position;
1405 }
1406
1407 let token = match tokenizer.parse_next() {
1408 Ok(token) => token,
1409 Err(e) => {
1410 let error_location = get_error_location(tokenizer);
1411 warnings.push(CssParseWarnMsg {
1412 warning: CssParseWarnMsgInner::ParseError(e.into()),
1413 location: ErrorLocationRange { start: last_error_location, end: error_location },
1414 });
1415 break;
1417 }
1418 };
1419
1420 macro_rules! warn_and_continue {
1421 ($warning:expr) => {{
1422 warnings.push(CssParseWarnMsg {
1423 warning: $warning,
1424 location: ErrorLocationRange { start: last_error_location, end: get_error_location(tokenizer) },
1425 });
1426 continue;
1427 }};
1428 }
1429
1430 fn get_parent_paths(nesting_stack: &[NestingLevel<'_>]) -> Vec<Vec<CssPathSelector>> {
1432 if let Some(parent) = nesting_stack.last() {
1433 parent.paths.clone()
1434 } else {
1435 Vec::new()
1436 }
1437 }
1438
1439 fn combine_paths(
1443 parent_paths: &[Vec<CssPathSelector>],
1444 child_path: &[CssPathSelector],
1445 is_pseudo_only: bool,
1446 ) -> Vec<Vec<CssPathSelector>> {
1447 if parent_paths.is_empty() {
1448 vec![child_path.to_vec()]
1449 } else {
1450 parent_paths
1451 .iter()
1452 .map(|parent| {
1453 let mut combined = parent.clone();
1454 if !is_pseudo_only && !child_path.is_empty() {
1455 combined.push(CssPathSelector::Children);
1457 }
1458 combined.extend(child_path.iter().cloned());
1459 combined
1460 })
1461 .collect()
1462 }
1463 }
1464
1465 match token {
1466 Token::AtRule(rule_name) => {
1467 pending_at_rule = Some(rule_name);
1469 pending_at_str_parts.clear();
1470 }
1471 Token::AtStr(content) => {
1472 if pending_at_rule.is_some() {
1474 if !content.eq_ignore_ascii_case("and") {
1476 pending_at_str_parts.push(content.to_string());
1477 }
1478 }
1479 }
1480 Token::BlockStart => {
1481 if let Some(rule_name) = pending_at_rule.take() {
1483 let combined_content = pending_at_str_parts.join(" and ");
1484 pending_at_str_parts.clear();
1485
1486 let conditions = match rule_name.to_lowercase().as_str() {
1487 "media" => parse_media_conditions(&combined_content),
1488 "lang" => parse_lang_condition(&combined_content).into_iter().collect(),
1489 "os" => crate::dynamic_selector::parse_os_at_rule_content(&combined_content).unwrap_or_default(),
1490 "theme" => parse_theme_condition(&combined_content).into_iter().collect(),
1491 "container" => parse_container_conditions(&combined_content),
1492 _ => {
1493 Vec::new()
1495 }
1496 };
1497
1498 if !conditions.is_empty() {
1499 at_rule_stack.push((conditions, block_nesting + 1));
1501 }
1502 }
1503
1504 block_nesting += 1;
1505
1506 if !current_paths.is_empty() || !last_path.is_empty() {
1508 if !last_path.is_empty() {
1510 current_paths.push(last_path.clone());
1511 last_path.clear();
1512 }
1513
1514 let parent_paths = get_parent_paths(&nesting_stack);
1516 let combined_paths: Vec<Vec<CssPathSelector>> = if parent_paths.is_empty() {
1517 current_paths.clone()
1518 } else {
1519 let mut result = Vec::new();
1521 for parent in &parent_paths {
1522 for child in ¤t_paths {
1523 let is_pseudo_only = child.first().map(|s| matches!(s, CssPathSelector::PseudoSelector(_))).unwrap_or(false);
1525 let mut combined = parent.clone();
1526 if !is_pseudo_only && !child.is_empty() {
1527 combined.push(CssPathSelector::Children);
1528 }
1529 combined.extend(child.iter().cloned());
1530 result.push(combined);
1531 }
1532 }
1533 result
1534 };
1535
1536 nesting_stack.push(NestingLevel {
1538 paths: combined_paths,
1539 declarations: std::mem::take(&mut current_declarations),
1540 nesting_level: block_nesting,
1541 });
1542 current_paths.clear();
1543 }
1544 }
1545 Token::Comma => {
1546 if !last_path.is_empty() {
1548 current_paths.push(last_path.clone());
1549 last_path.clear();
1550 }
1551 }
1552 Token::BlockEnd => {
1553 if block_nesting == 0 {
1554 warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1555 message: "Block end without matching block start"
1556 });
1557 }
1558
1559 let current_conditions: Vec<DynamicSelector> = at_rule_stack
1561 .iter()
1562 .flat_map(|(conds, _)| conds.iter().cloned())
1563 .collect();
1564
1565 while let Some((_, level)) = at_rule_stack.last() {
1567 if *level >= block_nesting {
1568 at_rule_stack.pop();
1569 } else {
1570 break;
1571 }
1572 }
1573
1574 block_nesting = block_nesting.saturating_sub(1);
1575
1576 if let Some(level) = nesting_stack.pop() {
1578 if !level.paths.is_empty() && !current_declarations.is_empty() {
1580 css_blocks.extend(level.paths.iter().map(|path| UnparsedCssRuleBlock {
1581 path: CssPath {
1582 selectors: path.clone().into(),
1583 },
1584 declarations: current_declarations.clone(),
1585 conditions: current_conditions.clone(),
1586 }));
1587 }
1588 current_declarations = level.declarations;
1590 }
1591
1592 last_path.clear();
1593 current_paths.clear();
1594 }
1595 Token::UniversalSelector => {
1596 last_path.push(CssPathSelector::Global);
1597 }
1598 Token::TypeSelector(div_type) => {
1599 match NodeTypeTag::from_str(div_type) {
1600 Ok(nt) => last_path.push(CssPathSelector::Type(nt)),
1601 Err(e) => {
1602 warn_and_continue!(CssParseWarnMsgInner::SkippedRule {
1603 selector: Some(div_type),
1604 error: e.into(),
1605 });
1606 }
1607 }
1608 }
1609 Token::IdSelector(id) => {
1610 last_path.push(CssPathSelector::Id(id.to_string().into()));
1611 }
1612 Token::ClassSelector(class) => {
1613 last_path.push(CssPathSelector::Class(class.to_string().into()));
1614 }
1615 Token::Combinator(Combinator::GreaterThan) => {
1616 last_path.push(CssPathSelector::DirectChildren);
1617 }
1618 Token::Combinator(Combinator::Space) => {
1619 last_path.push(CssPathSelector::Children);
1620 }
1621 Token::Combinator(Combinator::Plus) => {
1622 last_path.push(CssPathSelector::AdjacentSibling);
1623 }
1624 Token::Combinator(Combinator::Tilde) => {
1625 last_path.push(CssPathSelector::GeneralSibling);
1626 }
1627 Token::PseudoClass { selector, value } | Token::DoublePseudoClass { selector, value } => {
1628 match pseudo_selector_from_str(selector, value) {
1629 Ok(ps) => last_path.push(CssPathSelector::PseudoSelector(ps)),
1630 Err(e) => {
1631 warn_and_continue!(CssParseWarnMsgInner::SkippedRule {
1632 selector: Some(selector),
1633 error: e.into(),
1634 });
1635 }
1636 }
1637 }
1638 Token::AttributeSelector(attr) => {
1639 match parse_attribute_selector(attr) {
1640 Some(sel) => last_path.push(CssPathSelector::Attribute(sel)),
1641 None => warn_and_continue!(CssParseWarnMsgInner::MalformedStructure {
1642 message: "Malformed attribute selector, rule skipped",
1643 }),
1644 }
1645 }
1646 Token::Declaration(key, val) => {
1647 current_declarations.insert(
1648 key,
1649 (val, ErrorLocationRange { start: last_error_location, end: get_error_location(tokenizer) }),
1650 );
1651 }
1652 Token::EndOfStream => {
1653 if block_nesting != 0 {
1654 warnings.push(CssParseWarnMsg {
1655 warning: CssParseWarnMsgInner::MalformedStructure {
1656 message: "Unclosed blocks at end of file",
1657 },
1658 location: ErrorLocationRange { start: last_error_location, end: get_error_location(tokenizer) },
1659 });
1660 }
1661 break;
1662 }
1663 _ => { }
1664 }
1665
1666 last_error_location = get_error_location(tokenizer);
1667 }
1668
1669 let (stylesheet, mut block_warnings) = css_blocks_to_stylesheet(css_blocks, css_string);
1671 warnings.append(&mut block_warnings);
1672
1673 Ok((stylesheet, warnings))
1674}
1675
1676fn css_blocks_to_stylesheet<'a>(
1677 css_blocks: Vec<UnparsedCssRuleBlock<'a>>,
1678 css_string: &'a str,
1679) -> (Vec<CssRuleBlock>, Vec<CssParseWarnMsg<'a>>) {
1680 let css_key_map = crate::props::property::get_css_key_map();
1681 let mut warnings = Vec::new();
1682 let mut parsed_css_blocks = Vec::new();
1683
1684 for unparsed_css_block in css_blocks {
1685 let mut declarations = Vec::<CssDeclaration>::new();
1686
1687 for (unparsed_css_key, (unparsed_css_value, location)) in &unparsed_css_block.declarations {
1688 match parse_declaration_resilient(
1689 unparsed_css_key,
1690 unparsed_css_value,
1691 *location,
1692 &css_key_map,
1693 ) {
1694 Ok(decls) => declarations.extend(decls),
1695 Err(e) => {
1696 warnings.push(CssParseWarnMsg {
1697 warning: CssParseWarnMsgInner::SkippedDeclaration {
1698 key: unparsed_css_key,
1699 value: unparsed_css_value,
1700 error: e,
1701 },
1702 location: *location,
1703 });
1704 }
1705 }
1706 }
1707
1708 parsed_css_blocks.push(CssRuleBlock {
1709 path: unparsed_css_block.path,
1710 declarations: declarations.into(),
1711 conditions: unparsed_css_block.conditions.into(),
1712 priority: crate::css::rule_priority::AUTHOR,
1713 });
1714 }
1715
1716 (parsed_css_blocks, warnings)
1717}
1718
1719fn parse_declaration_resilient<'a>(
1720 unparsed_css_key: &'a str,
1721 unparsed_css_value: &'a str,
1722 location: ErrorLocationRange,
1723 css_key_map: &CssKeyMap,
1724) -> Result<Vec<CssDeclaration>, CssParseErrorInner<'a>> {
1725 let mut declarations = Vec::new();
1726
1727 if let Some(combined_key) = CombinedCssPropertyType::from_str(unparsed_css_key, css_key_map) {
1728 if check_if_value_is_css_var(unparsed_css_value).is_some() {
1729 return Err(CssParseErrorInner::VarOnShorthandProperty {
1730 key: combined_key,
1731 value: unparsed_css_value,
1732 });
1733 }
1734
1735 match parse_combined_css_property(combined_key, unparsed_css_value) {
1737 Ok(parsed_props) => {
1738 declarations.extend(parsed_props.into_iter().map(CssDeclaration::Static));
1739 }
1740 Err(e) => return Err(CssParseErrorInner::DynamicCssParseError(e.into())),
1741 }
1742 } else if let Some(normal_key) = CssPropertyType::from_str(unparsed_css_key, css_key_map) {
1743 if let Some(css_var) = check_if_value_is_css_var(unparsed_css_value) {
1744 let (css_var_id, css_var_default) = css_var?;
1745 match parse_css_property(normal_key, css_var_default) {
1746 Ok(parsed_default) => {
1747 declarations.push(CssDeclaration::Dynamic(DynamicCssProperty {
1748 dynamic_id: css_var_id.to_string().into(),
1749 default_value: parsed_default,
1750 }));
1751 }
1752 Err(e) => return Err(CssParseErrorInner::DynamicCssParseError(e.into())),
1753 }
1754 } else {
1755 match parse_css_property(normal_key, unparsed_css_value) {
1756 Ok(parsed_value) => {
1757 declarations.push(CssDeclaration::Static(parsed_value));
1758 }
1759 Err(e) => return Err(CssParseErrorInner::DynamicCssParseError(e.into())),
1760 }
1761 }
1762 } else {
1763 return Err(CssParseErrorInner::UnknownPropertyKey(
1764 unparsed_css_key,
1765 unparsed_css_value,
1766 ));
1767 }
1768
1769 Ok(declarations)
1770}
1771
1772pub fn parse_css_declaration<'a>(
1778 unparsed_css_key: &'a str,
1779 unparsed_css_value: &'a str,
1780 location: ErrorLocationRange,
1781 css_key_map: &CssKeyMap,
1782 warnings: &mut Vec<CssParseWarnMsg<'a>>,
1783 declarations: &mut Vec<CssDeclaration>,
1784) -> Result<(), CssParseErrorInner<'a>> {
1785 match parse_declaration_resilient(unparsed_css_key, unparsed_css_value, location, css_key_map) {
1786 Ok(mut decls) => {
1787 declarations.append(&mut decls);
1788 Ok(())
1789 }
1790 Err(e) => {
1791 if let CssParseErrorInner::UnknownPropertyKey(key, val) = &e {
1792 warnings.push(CssParseWarnMsg {
1793 warning: CssParseWarnMsgInner::UnsupportedKeyValuePair { key, value: val },
1794 location,
1795 });
1796 Ok(()) } else {
1798 Err(e) }
1800 }
1801 }
1802}
1803
1804fn check_if_value_is_css_var<'a>(
1805 unparsed_css_value: &'a str,
1806) -> Option<Result<(&'a str, &'a str), CssParseErrorInner<'a>>> {
1807 const DEFAULT_VARIABLE_DEFAULT: &str = "none";
1808
1809 let (_, brace_contents) = parse_parentheses(unparsed_css_value, &["var"]).ok()?;
1810
1811 Some(match parse_css_variable_brace_contents(brace_contents) {
1813 Some((variable_id, default_value)) => Ok((
1814 variable_id,
1815 default_value.unwrap_or(DEFAULT_VARIABLE_DEFAULT),
1816 )),
1817 None => Err(DynamicCssParseError::InvalidBraceContents(brace_contents).into()),
1818 })
1819}
1820
1821fn parse_css_variable_brace_contents(input: &str) -> Option<(&str, Option<&str>)> {
1828 let input = input.trim();
1829
1830 let mut split_comma_iter = input.splitn(2, ",");
1831 let var_name = split_comma_iter.next()?;
1832 let var_name = var_name.trim();
1833
1834 if !var_name.starts_with("--") {
1835 return None; }
1837
1838 Some((&var_name[2..], split_comma_iter.next()))
1839}