1use std::{
3 num::ParseIntError,
4 fmt,
5 collections::HashMap,
6};
7pub use azul_simplecss::Error as CssSyntaxError;
8use azul_simplecss::Tokenizer;
9
10use crate::css_parser;
11pub use crate::css_parser::CssParsingError;
12use azul_css::{
13 Css, CssDeclaration, Stylesheet, DynamicCssProperty,
14 CssPropertyType, CssRuleBlock, CssPath, CssPathSelector,
15 CssNthChildSelector, CssPathPseudoSelector, CssNthChildSelector::*,
16 NodeTypePath, NodeTypePathParseError, CombinedCssPropertyType, CssKeyMap,
17};
18
19#[derive(Debug, Clone, PartialEq)]
21pub struct CssParseError<'a> {
22 pub css_string: &'a str,
23 pub error: CssParseErrorInner<'a>,
24 pub location: (ErrorLocation, ErrorLocation),
25}
26
27impl<'a> CssParseError<'a> {
28 pub fn get_error_string(&self) -> &'a str {
30 let (start, end) = (self.location.0.original_pos, self.location.1.original_pos);
31 let s = &self.css_string[start..end];
32 s.trim()
33 }
34}
35
36#[derive(Debug, Clone, PartialEq)]
37pub enum CssParseErrorInner<'a> {
38 ParseError(CssSyntaxError),
40 UnclosedBlock,
42 MalformedCss,
44 DynamicCssParseError(DynamicCssParseError<'a>),
47 PseudoSelectorParseError(CssPseudoSelectorParseError<'a>),
49 NodeTypePath(NodeTypePathParseError<'a>),
51 UnknownPropertyKey(&'a str, &'a str),
53 VarOnShorthandProperty { key: CombinedCssPropertyType, value: &'a str },
58}
59
60impl_display!{ CssParseErrorInner<'a>, {
61 ParseError(e) => format!("Parse Error: {:?}", e),
62 UnclosedBlock => "Unclosed block",
63 MalformedCss => "Malformed Css",
64 DynamicCssParseError(e) => format!("{}", e),
65 PseudoSelectorParseError(e) => format!("Failed to parse pseudo-selector: {}", e),
66 NodeTypePath(e) => format!("Failed to parse CSS selector path: {}", e),
67 UnknownPropertyKey(k, v) => format!("Unknown CSS key: \"{}: {}\"", k, v),
68 VarOnShorthandProperty { key, value } => format!(
69 "Error while parsing: \"{}: {};\": var() cannot be used on shorthand properties - use `{}-top` or `{}-x` as the key instead: ",
70 key, value, key, key
71 ),
72}}
73
74impl<'a> From<CssSyntaxError> for CssParseErrorInner<'a> {
75 fn from(e: CssSyntaxError) -> Self {
76 CssParseErrorInner::ParseError(e)
77 }
78}
79
80impl_from! { DynamicCssParseError<'a>, CssParseErrorInner::DynamicCssParseError }
81impl_from! { NodeTypePathParseError<'a>, CssParseErrorInner::NodeTypePath }
82impl_from! { CssPseudoSelectorParseError<'a>, CssParseErrorInner::PseudoSelectorParseError }
83
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub enum CssPseudoSelectorParseError<'a> {
86 EmptyNthChild,
87 UnknownSelector(&'a str, Option<&'a str>),
88 InvalidNthChildPattern(&'a str),
89 InvalidNthChild(ParseIntError),
90}
91
92impl<'a> From<ParseIntError> for CssPseudoSelectorParseError<'a> {
93 fn from(e: ParseIntError) -> Self { CssPseudoSelectorParseError::InvalidNthChild(e) }
94}
95
96impl_display! { CssPseudoSelectorParseError<'a>, {
97 EmptyNthChild => format!("\
98 Empty :nth-child() selector - nth-child() must at least take a number, \
99 a pattern (such as \"2n+3\") or the values \"even\" or \"odd\"."
100 ),
101 UnknownSelector(selector, value) => {
102 let format_str = match value {
103 Some(v) => format!("{}({})", selector, v),
104 None => format!("{}", selector),
105 };
106 format!("Invalid or unknown CSS pseudo-selector: ':{}'", format_str)
107 },
108 InvalidNthChildPattern(selector) => format!(
109 "Invalid pseudo-selector :{} - value has to be a \
110 number, \"even\" or \"odd\" or a pattern such as \"2n+3\"", selector
111 ),
112 InvalidNthChild(e) => format!("Invalid :nth-child pseudo-selector: ':{}'", e),
113}}
114
115#[derive(Debug, Clone, PartialEq)]
117pub enum DynamicCssParseError<'a> {
118 InvalidBraceContents(&'a str),
120 UnexpectedValue(CssParsingError<'a>),
122}
123
124impl_display!{ DynamicCssParseError<'a>, {
125 InvalidBraceContents(e) => format!("Invalid contents of var() function: var({})", e),
126 UnexpectedValue(e) => format!("{}", e),
127}}
128
129impl<'a> From<CssParsingError<'a>> for DynamicCssParseError<'a> {
130 fn from(e: CssParsingError<'a>) -> Self {
131 DynamicCssParseError::UnexpectedValue(e)
132 }
133}
134
135fn pseudo_selector_from_str<'a>(selector: &'a str, value: Option<&'a str>)
138-> Result<CssPathPseudoSelector, CssPseudoSelectorParseError<'a>>
139{
140 match selector {
141 "first" => Ok(CssPathPseudoSelector::First),
142 "last" => Ok(CssPathPseudoSelector::Last),
143 "hover" => Ok(CssPathPseudoSelector::Hover),
144 "active" => Ok(CssPathPseudoSelector::Active),
145 "focus" => Ok(CssPathPseudoSelector::Focus),
146 "nth-child" => {
147 let value = value.ok_or(CssPseudoSelectorParseError::EmptyNthChild)?;
148 let parsed = parse_nth_child_selector(value)?;
149 Ok(CssPathPseudoSelector::NthChild(parsed))
150 },
151 _ => {
152 Err(CssPseudoSelectorParseError::UnknownSelector(selector, value))
153 },
154 }
155}
156
157fn parse_nth_child_selector<'a>(value: &'a str) -> Result<CssNthChildSelector, CssPseudoSelectorParseError<'a>> {
161
162 let value = value.trim();
163
164 if value.is_empty() {
165 return Err(CssPseudoSelectorParseError::EmptyNthChild);
166 }
167
168 if let Ok(number) = value.parse::<usize>() {
169 return Ok(Number(number));
170 }
171
172 match value.as_ref() {
174 "even" => Ok(Even),
175 "odd" => Ok(Odd),
176 other => parse_nth_child_pattern(value),
177 }
178}
179
180fn parse_nth_child_pattern<'a>(value: &'a str) -> Result<CssNthChildSelector, CssPseudoSelectorParseError<'a>> {
182
183 let value = value.trim();
184
185 if value.is_empty() {
186 return Err(CssPseudoSelectorParseError::EmptyNthChild);
187 }
188
189 let repeat = value.split("n").next()
191 .ok_or(CssPseudoSelectorParseError::InvalidNthChildPattern(value))?
192 .trim()
193 .parse::<usize>()?;
194
195 let mut offset_iterator = value.split("+");
197
198 offset_iterator.next().unwrap();
200
201 let offset = match offset_iterator.next() {
202 Some(offset_string) => {
203 let offset_string = offset_string.trim();
204 if offset_string.is_empty() {
205 return Err(CssPseudoSelectorParseError::InvalidNthChildPattern(value));
206 } else {
207 offset_string.parse::<usize>()?
208 }
209 },
210 None => 0,
211 };
212
213 Ok(Pattern { repeat, offset })
214}
215
216#[test]
217fn test_css_pseudo_selector_parse() {
218
219 use self::CssPathPseudoSelector::*;
220 use self::CssPseudoSelectorParseError::*;
221
222 let ok_res = [
223 (("first", None), First),
224 (("last", None), Last),
225 (("hover", None), Hover),
226 (("active", None), Active),
227 (("focus", None), Focus),
228 (("nth-child", Some("4")), NthChild(Number(4))),
229 (("nth-child", Some("even")), NthChild(Even)),
230 (("nth-child", Some("odd")), NthChild(Odd)),
231 (("nth-child", Some("5n")), NthChild(Pattern { repeat: 5, offset: 0 })),
232 (("nth-child", Some("2n+3")), NthChild(Pattern { repeat: 2, offset: 3 })),
233 ];
234
235 let err = [
236 (("asdf", None), UnknownSelector("asdf", None)),
237 (("", None), UnknownSelector("", None)),
238 (("nth-child", Some("2n+")), InvalidNthChildPattern("2n+")),
239 ];
242
243 for ((selector, val), a) in &ok_res {
244 assert_eq!(pseudo_selector_from_str(selector, *val), Ok(*a));
245 }
246
247 for ((selector, val), e) in &err {
248 assert_eq!(pseudo_selector_from_str(selector, *val), Err(e.clone()));
249 }
250}
251
252#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
253pub struct ErrorLocation {
254 pub original_pos: usize,
255}
256
257impl ErrorLocation {
258 pub fn get_line_column_from_error(&self, css_string: &str) -> (usize, usize) {
260
261 let error_location = self.original_pos.saturating_sub(1);
262 let (mut line_number, mut total_characters) = (0, 0);
263
264 for line in css_string[0..error_location].lines() {
265 line_number += 1;
266 total_characters += line.chars().count();
267 }
268
269 let total_characters = total_characters + line_number;
271 let column_pos = error_location - total_characters.saturating_sub(2);
272
273 (line_number, column_pos)
274 }
275}
276
277impl<'a> fmt::Display for CssParseError<'a> {
278 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
279 let start_location = self.location.0.get_line_column_from_error(self.css_string);
280 let end_location = self.location.1.get_line_column_from_error(self.css_string);
281 write!(f, " start: line {}:{}\r\n end: line {}:{}\r\n text: \"{}\"\r\n reason: {}",
282 start_location.0, start_location.1,
283 end_location.0, end_location.1,
284 self.get_error_string(),
285 self.error,
286 )
287 }
288}
289
290pub fn new_from_str<'a>(css_string: &'a str) -> Result<Css, CssParseError<'a>> {
291 let mut tokenizer = Tokenizer::new(css_string);
292 let (stylesheet, _warnings) = new_from_str_inner(css_string, &mut tokenizer)?;
293 Ok(Css { stylesheets: vec![stylesheet] })
294}
295
296fn get_error_location(tokenizer: &Tokenizer) -> ErrorLocation {
298 ErrorLocation {
299 original_pos: tokenizer.pos(),
300 }
301}
302
303#[derive(Debug, Clone, PartialEq)]
304pub enum CssPathParseError<'a> {
305 EmptyPath,
306 InvalidTokenEncountered(&'a str),
308 UnexpectedEndOfStream(&'a str),
309 SyntaxError(CssSyntaxError),
310 NodeTypePath(NodeTypePathParseError<'a>),
312 PseudoSelectorParseError(CssPseudoSelectorParseError<'a>),
314}
315
316impl_from! { NodeTypePathParseError<'a>, CssPathParseError::NodeTypePath }
317impl_from! { CssPseudoSelectorParseError<'a>, CssPathParseError::PseudoSelectorParseError }
318
319impl<'a> From<CssSyntaxError> for CssPathParseError<'a> {
320 fn from(e: CssSyntaxError) -> Self {
321 CssPathParseError::SyntaxError(e)
322 }
323}
324
325pub fn parse_css_path<'a>(input: &'a str) -> Result<CssPath, CssPathParseError<'a>> {
350
351 use azul_simplecss::{Token, Combinator};
352
353 let input = input.trim();
354 if input.is_empty() {
355 return Err(CssPathParseError::EmptyPath);
356 }
357
358 let mut tokenizer = Tokenizer::new(input);
359 let mut selectors = Vec::new();
360
361 loop {
362 let token = tokenizer.parse_next()?;
363 match token {
364 Token::UniversalSelector => {
365 selectors.push(CssPathSelector::Global);
366 },
367 Token::TypeSelector(div_type) => {
368 selectors.push(CssPathSelector::Type(NodeTypePath::from_str(div_type)?));
369 },
370 Token::IdSelector(id) => {
371 selectors.push(CssPathSelector::Id(id.to_string()));
372 },
373 Token::ClassSelector(class) => {
374 selectors.push(CssPathSelector::Class(class.to_string()));
375 },
376 Token::Combinator(Combinator::GreaterThan) => {
377 selectors.push(CssPathSelector::DirectChildren);
378 },
379 Token::Combinator(Combinator::Space) => {
380 selectors.push(CssPathSelector::Children);
381 },
382 Token::PseudoClass { selector, value } => {
383 selectors.push(CssPathSelector::PseudoSelector(pseudo_selector_from_str(selector, value)?));
384 },
385 Token::EndOfStream => {
386 break;
387 }
388 _ => {
389 return Err(CssPathParseError::InvalidTokenEncountered(input));
390 }
391 }
392 }
393
394 if !selectors.is_empty() {
395 Ok(CssPath { selectors })
396 } else {
397 Err(CssPathParseError::EmptyPath)
398 }
399}
400
401#[derive(Debug, Clone, PartialEq)]
402pub struct UnparsedCssRuleBlock<'a> {
403 pub path: CssPath,
405 pub declarations: HashMap<&'a str, (&'a str, (ErrorLocation, ErrorLocation))>,
407}
408
409#[derive(Debug, Clone, PartialEq)]
410pub struct CssParseWarnMsg<'a> {
411 warning: CssParseWarnMsgInner<'a>,
412 location: (ErrorLocation, ErrorLocation),
413}
414
415#[derive(Debug, Clone, PartialEq)]
416pub enum CssParseWarnMsgInner<'a> {
417 UnsupportedKeyValuePair { key: &'a str, value: &'a str },
419}
420
421fn new_from_str_inner<'a>(css_string: &'a str, tokenizer: &mut Tokenizer<'a>)
427-> Result<(Stylesheet, Vec<CssParseWarnMsg<'a>>), CssParseError<'a>> {
428
429 use azul_simplecss::{Token, Combinator};
430
431 let mut css_blocks = Vec::new();
432
433 let mut parser_in_block = false;
435 let mut block_nesting = 0_usize;
436
437 let mut current_paths = Vec::new();
441 let mut current_rules = HashMap::<&str, (&str, (ErrorLocation, ErrorLocation))>::new();
443 let mut last_path = Vec::new();
445
446 let mut last_error_location = ErrorLocation { original_pos: 0 };
447
448 loop {
449
450 let token = tokenizer.parse_next().map_err(|e| CssParseError {
451 css_string,
452 error: e.into(),
453 location: (last_error_location, get_error_location(tokenizer))
454 })?;
455
456 macro_rules! check_parser_is_outside_block {() => {
457 if parser_in_block {
458 return Err(CssParseError {
459 css_string,
460 error: CssParseErrorInner::MalformedCss,
461 location: (last_error_location, get_error_location(tokenizer)),
462 });
463 }
464 }}
465
466 macro_rules! check_parser_is_inside_block {() => {
467 if !parser_in_block {
468 return Err(CssParseError {
469 css_string,
470 error: CssParseErrorInner::MalformedCss,
471 location: (last_error_location, get_error_location(tokenizer)),
472 });
473 }
474 }}
475
476 match token {
477 Token::BlockStart => {
478 check_parser_is_outside_block!();
479 parser_in_block = true;
480 block_nesting += 1;
481 current_paths.push(last_path.clone());
482 last_path.clear();
483 },
484 Token::Comma => {
485 check_parser_is_outside_block!();
486 current_paths.push(last_path.clone());
487 last_path.clear();
488 },
489 Token::BlockEnd => {
490
491 block_nesting -= 1;
492 check_parser_is_inside_block!();
493 parser_in_block = false;
494
495 css_blocks.extend(current_paths.drain(..).map(|path| {
496 UnparsedCssRuleBlock {
497 path: CssPath { selectors: path },
498 declarations: current_rules.clone(),
499 }
500 }));
501
502 current_rules.clear();
503 last_path.clear(); },
505
506 Token::UniversalSelector => {
508 check_parser_is_outside_block!();
509 last_path.push(CssPathSelector::Global);
510 },
511 Token::TypeSelector(div_type) => {
512 check_parser_is_outside_block!();
513 last_path.push(CssPathSelector::Type(NodeTypePath::from_str(div_type).map_err(|e| {
514 CssParseError {
515 css_string,
516 error: e.into(),
517 location: (last_error_location, get_error_location(tokenizer)),
518 }
519 })?));
520 },
521 Token::IdSelector(id) => {
522 check_parser_is_outside_block!();
523 last_path.push(CssPathSelector::Id(id.to_string()));
524 },
525 Token::ClassSelector(class) => {
526 check_parser_is_outside_block!();
527 last_path.push(CssPathSelector::Class(class.to_string()));
528 },
529 Token::Combinator(Combinator::GreaterThan) => {
530 check_parser_is_outside_block!();
531 last_path.push(CssPathSelector::DirectChildren);
532 },
533 Token::Combinator(Combinator::Space) => {
534 check_parser_is_outside_block!();
535 last_path.push(CssPathSelector::Children);
536 },
537 Token::PseudoClass { selector, value } => {
538 check_parser_is_outside_block!();
539 last_path.push(CssPathSelector::PseudoSelector(pseudo_selector_from_str(selector, value).map_err(|e| {
540 CssParseError {
541 css_string,
542 error: e.into(),
543 location: (last_error_location, get_error_location(tokenizer)),
544 }
545 })?));
546 },
547 Token::Declaration(key, val) => {
548 check_parser_is_inside_block!();
549 current_rules.insert(key, (val, (last_error_location, get_error_location(tokenizer))));
550 },
551 Token::EndOfStream => {
552
553 if block_nesting != 0 {
555 return Err(CssParseError {
556 css_string,
557 error: CssParseErrorInner::UnclosedBlock,
558 location: (last_error_location, get_error_location(tokenizer)),
559 });
560 }
561
562 break;
563 },
564 _ => {
565 }
567 }
568
569 last_error_location = get_error_location(tokenizer);
570 }
571
572 unparsed_css_blocks_to_stylesheet(css_blocks, css_string)
573}
574
575fn unparsed_css_blocks_to_stylesheet<'a>(css_blocks: Vec<UnparsedCssRuleBlock<'a>>, css_string: &'a str)
576-> Result<(Stylesheet, Vec<CssParseWarnMsg<'a>>), CssParseError<'a>> {
577
578 let css_key_map = azul_css::get_css_key_map();
580
581 let mut warnings = Vec::new();
582
583 let parsed_css_blocks = css_blocks.into_iter().map(|unparsed_css_block| {
584
585 let mut declarations = Vec::<CssDeclaration>::new();
586
587 for (unparsed_css_key, (unparsed_css_value, location)) in unparsed_css_block.declarations {
588 parse_css_declaration(
589 unparsed_css_key,
590 unparsed_css_value,
591 location,
592 &css_key_map,
593 &mut warnings,
594 &mut declarations,
595 ).map_err(|e| CssParseError {
596 css_string,
597 error: e.into(),
598 location,
599 })?;
600 }
601
602 Ok(CssRuleBlock {
603 path: unparsed_css_block.path,
604 declarations,
605 })
606 }).collect::<Result<Vec<CssRuleBlock>, CssParseError>>()?;
607
608 Ok((parsed_css_blocks.into(), warnings))
609}
610
611fn parse_css_declaration<'a>(
612 unparsed_css_key: &'a str,
613 unparsed_css_value: &'a str,
614 location: (ErrorLocation, ErrorLocation),
615 css_key_map: &CssKeyMap,
616 warnings: &mut Vec<CssParseWarnMsg<'a>>,
617 declarations: &mut Vec<CssDeclaration>,
618) -> Result<(), CssParseErrorInner<'a>> {
619
620 use self::CssParseErrorInner::*;
621 use self::CssParseWarnMsgInner::*;
622
623 if let Some(combined_key) = CombinedCssPropertyType::from_str(unparsed_css_key, &css_key_map) {
624 if let Some(css_var) = check_if_value_is_css_var(unparsed_css_value) {
625 return Err(VarOnShorthandProperty { key: combined_key, value: unparsed_css_value });
627 } else {
628 let parsed_css_properties =
630 css_parser::parse_combined_css_property(combined_key, unparsed_css_value)
631 .map_err(|e| DynamicCssParseError(e.into()))?;
632
633 declarations.extend(parsed_css_properties.into_iter().map(|val| CssDeclaration::Static(val)));
634 }
635 } else if let Some(normal_key) = CssPropertyType::from_str(unparsed_css_key, css_key_map) {
636 if let Some(css_var) = check_if_value_is_css_var(unparsed_css_value) {
637 let (css_var_id, css_var_default) = css_var?;
639 let parsed_default_value =
640 css_parser::parse_css_property(normal_key, css_var_default)
641 .map_err(|e| DynamicCssParseError(e.into()))?;
642
643 declarations.push(CssDeclaration::Dynamic(DynamicCssProperty {
644 dynamic_id: css_var_id.to_string(),
645 default_value: parsed_default_value,
646 }));
647 } else {
648 let parsed_css_value =
650 css_parser::parse_css_property(normal_key, unparsed_css_value)
651 .map_err(|e| DynamicCssParseError(e.into()))?;
652
653 declarations.push(CssDeclaration::Static(parsed_css_value));
654 }
655 } else {
656 warnings.push(CssParseWarnMsg {
658 warning: UnsupportedKeyValuePair { key: unparsed_css_key, value: unparsed_css_value },
659 location,
660 });
661 }
662
663 Ok(())
664}
665
666fn check_if_value_is_css_var<'a>(unparsed_css_value: &'a str) -> Option<Result<(&'a str, &'a str), CssParseErrorInner<'a>>> {
667
668 const DEFAULT_VARIABLE_DEFAULT: &str = "none";
669
670 let (_, brace_contents) = css_parser::parse_parentheses(unparsed_css_value, &["var"]).ok()?;
671
672 Some(match parse_css_variable_brace_contents(brace_contents) {
674 Some((variable_id, default_value)) => Ok((variable_id, default_value.unwrap_or(DEFAULT_VARIABLE_DEFAULT))),
675 None => Err(DynamicCssParseError::InvalidBraceContents(brace_contents).into()),
676 })
677}
678
679fn parse_css_variable_brace_contents<'a>(input: &'a str) -> Option<(&'a str, Option<&'a str>)> {
686
687 let input = input.trim();
688
689 let mut split_comma_iter = input.splitn(2, ",");
690 let var_name = split_comma_iter.next()?;
691 let var_name = var_name.trim();
692
693 if !var_name.starts_with("--") {
694 return None; }
696
697 Some((&var_name[2..], split_comma_iter.next()))
698}
699
700#[test]
701fn test_css_parse_1() {
702
703 use azul_css::*;
704
705 let parsed_css = new_from_str("
706 div#my_id .my_class:first {
707 background-color: red;
708 }
709 ").unwrap();
710
711
712 let expected_css_rules = vec![CssRuleBlock {
713 path: CssPath {
714 selectors: vec![
715 CssPathSelector::Type(NodeTypePath::Div),
716 CssPathSelector::Id(String::from("my_id")),
717 CssPathSelector::Children,
718 CssPathSelector::Class(String::from("my_class")),
721 CssPathSelector::PseudoSelector(CssPathPseudoSelector::First),
722 ],
723 },
724 declarations: vec![CssDeclaration::Static(CssProperty::BackgroundContent(
725 CssPropertyValue::Exact(StyleBackgroundContent::Color(ColorU {
726 r: 255,
727 g: 0,
728 b: 0,
729 a: 255,
730 })),
731 ))],
732 }];
733
734 assert_eq!(
735 parsed_css,
736 Css {
737 stylesheets: vec![expected_css_rules.into()]
738 }
739 );
740}
741
742#[test]
743fn test_css_simple_selector_parse() {
744 use self::CssPathSelector::*;
745 use azul_css::NodeTypePath;
746 let css = "div#id.my_class > p .new { }";
747 let parsed = vec![
748 Type(NodeTypePath::Div),
749 Id("id".into()),
750 Class("my_class".into()),
751 DirectChildren,
752 Type(NodeTypePath::P),
753 Children,
754 Class("new".into())
755 ];
756 assert_eq!(new_from_str(css).unwrap(), Css {
757 stylesheets: vec![Stylesheet {
758 rules: vec![CssRuleBlock {
759 path: CssPath { selectors: parsed },
760 declarations: Vec::new(),
761 }],
762 }],
763 });
764}
765
766#[cfg(test)]
767mod stylesheet_parse {
768
769 use azul_css::*;
770 use super::*;
771
772 fn test_css(css: &str, expected: Vec<CssRuleBlock>) {
773 let css = new_from_str(css).unwrap();
774 assert_eq!(css, Css { stylesheets: vec![expected.into()] });
775 }
776
777 #[test]
779 fn test_apply_css_pure_class() {
780 let red = CssProperty::BackgroundContent(CssPropertyValue::Exact(
781 StyleBackgroundContent::Color(ColorU {
782 r: 255,
783 g: 0,
784 b: 0,
785 a: 255,
786 }),
787 ));
788 let blue = CssProperty::BackgroundContent(CssPropertyValue::Exact(
789 StyleBackgroundContent::Color(ColorU {
790 r: 0,
791 g: 0,
792 b: 255,
793 a: 255,
794 }),
795 ));
796 let black = CssProperty::BackgroundContent(CssPropertyValue::Exact(
797 StyleBackgroundContent::Color(ColorU {
798 r: 0,
799 g: 0,
800 b: 0,
801 a: 255,
802 }),
803 ));
804
805 {
807 let css_1 = ".my_class { background-color: red; }";
808 let expected_rules = vec![
809 CssRuleBlock {
810 path: CssPath { selectors: vec![CssPathSelector::Class("my_class".into())] },
811 declarations: vec![
812 CssDeclaration::Static(red.clone())
813 ],
814 },
815 ];
816 test_css(css_1, expected_rules);
817 }
818
819 {
821 let css_2 = "#my_id { background-color: red; } .my_class { background-color: blue; }";
822 let expected_rules = vec![
823 CssRuleBlock {
824 path: CssPath { selectors: vec![CssPathSelector::Id("my_id".into())] },
825 declarations: vec![CssDeclaration::Static(red.clone())]
826 },
827 CssRuleBlock {
828 path: CssPath { selectors: vec![CssPathSelector::Class("my_class".into())] },
829 declarations: vec![CssDeclaration::Static(blue.clone())]
830 },
831 ];
832 test_css(css_2, expected_rules);
833 }
834
835 {
837 let css_3 = "* { background-color: black; } .my_class#my_id { background-color: red; } .my_class { background-color: blue; }";
838 let expected_rules = vec![
839 CssRuleBlock {
840 path: CssPath { selectors: vec![CssPathSelector::Global] },
841 declarations: vec![CssDeclaration::Static(black.clone())]
842 },
843 CssRuleBlock {
844 path: CssPath { selectors: vec![CssPathSelector::Class("my_class".into()), CssPathSelector::Id("my_id".into())] },
845 declarations: vec![CssDeclaration::Static(red.clone())]
846 },
847 CssRuleBlock {
848 path: CssPath { selectors: vec![CssPathSelector::Class("my_class".into())] },
849 declarations: vec![CssDeclaration::Static(blue.clone())]
850 },
851 ];
852 test_css(css_3, expected_rules);
853 }
854 }
855}
856
857#[test]
859fn test_multiple_rules() {
860 use azul_css::*;
861 use self::CssPathSelector::*;
862
863 let parsed_css = new_from_str("
864 * { }
865 * div.my_class#my_id { }
866 * div#my_id { }
867 * #my_id { }
868 div.my_class.specific#my_id { }
869 ").unwrap();
870
871 let expected_rules = vec![
872 CssRuleBlock { path: CssPath { selectors: vec![Global] }, declarations: Vec::new() },
874 CssRuleBlock { path: CssPath { selectors: vec![Global, Type(NodeTypePath::Div), Class("my_class".into()), Id("my_id".into())] }, declarations: Vec::new() },
875 CssRuleBlock { path: CssPath { selectors: vec![Global, Type(NodeTypePath::Div), Id("my_id".into())] }, declarations: Vec::new() },
876 CssRuleBlock { path: CssPath { selectors: vec![Global, Id("my_id".into())] }, declarations: Vec::new() },
877 CssRuleBlock { path: CssPath { selectors: vec![Type(NodeTypePath::Div), Class("my_class".into()), Class("specific".into()), Id("my_id".into())] }, declarations: Vec::new() },
878 ];
879
880 assert_eq!(parsed_css, Css { stylesheets: vec![expected_rules.into()] });
881}
882
883#[test]
884fn test_case_issue_93() {
885
886 use azul_css::*;
887 use self::CssPathSelector::*;
888
889 let parsed_css = new_from_str("
890 .tabwidget-tab-label {
891 color: #FFFFFF;
892 }
893
894 .tabwidget-tab.active .tabwidget-tab-label {
895 color: #000000;
896 }
897
898 .tabwidget-tab.active .tabwidget-tab-close {
899 color: #FF0000;
900 }
901 ").unwrap();
902
903 fn declaration(classes: &[CssPathSelector], color: ColorU) -> CssRuleBlock {
904 CssRuleBlock {
905 path: CssPath {
906 selectors: classes.to_vec(),
907 },
908 declarations: vec![CssDeclaration::Static(CssProperty::TextColor(
909 CssPropertyValue::Exact(StyleTextColor(color)),
910 ))],
911 }
912 }
913
914 let expected_rules = vec![
915 declaration(&[Class("tabwidget-tab-label".into())], ColorU { r: 255, g: 255, b: 255, a: 255 }),
916 declaration(&[Class("tabwidget-tab".into()), Class("active".into()), Children, Class("tabwidget-tab-label".into())], ColorU { r: 0, g: 0, b: 0, a: 255 }),
917 declaration(&[Class("tabwidget-tab".into()), Class("active".into()), Children, Class("tabwidget-tab-close".into())], ColorU { r: 255, g: 0, b: 0, a: 255 }),
918 ];
919
920 assert_eq!(parsed_css, Css { stylesheets: vec![expected_rules.into()] });
921}