1use std::collections::VecDeque;
19use std::fmt;
20
21use crate::engine::css::values::CssIdent;
22
23use super::tokenizer::{Token, Tokenizer};
24use super::values::{CssValue, Unit};
25
26#[derive(Debug, Clone)]
31pub enum CssNodeType {
32 Stylesheet,
34
35 Rule {
37 selectors: Vec<ComplexSelector>,
39 },
40
41 AtRule {
43 name: String,
45
46 params: AtQuery,
47 },
48
49 Declaration {
51 name: String,
53
54 value: CssValue,
55 },
56}
57
58#[derive(Debug, Clone, PartialEq)]
59pub enum AtQuery {
60 Keyword(String), Condition {
62 name: String, value: CssValue, },
65 Range {
66 left: Option<(CssValue, RangeOperator)>,
67 name: String,
68 right: Option<(RangeOperator, CssValue)>,
69 },
70 Group(Vec<AtQuery>), }
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
74pub enum RangeOperator {
75 Less,
76 LessEqual,
77 Equal,
78 GreaterEqual,
79 Greater,
80}
81
82#[derive(Debug)]
87pub struct CssNode {
88 node: CssNodeType,
90
91 children: Vec<CssNode>,
93}
94
95impl CssNode {
96 pub fn node(&self) -> &CssNodeType {
97 &self.node
98 }
99 pub fn children(&self) -> &Vec<CssNode> {
100 &self.children
101 }
102}
103
104#[derive(Debug, Clone, PartialEq, Eq, Hash)]
105pub struct Selector {
106 pub is_nesting: bool,
113
114 pub tag: Option<String>,
119
120 pub id: Option<String>,
122
123 pub classes: Vec<String>,
125
126 pub attributes: Vec<AttributeSelector>,
128
129 pub pseudo_classes: Vec<PseudoClass>,
131
132 pub pseudo_element: Option<String>,
134}
135
136impl Selector {
137 fn is_compound(&self) -> bool {
140 self.tag.is_some()
141 || self.id.is_some()
142 || !self.classes.is_empty()
143 || !self.attributes.is_empty()
144 || !self.pseudo_classes.is_empty()
145 || self.pseudo_element.is_some()
146 }
147
148 fn merge_from(&mut self, other: &Selector) {
150 if let Some(tag) = &other.tag {
151 self.tag = Some(tag.clone());
152 }
153 if let Some(id) = &other.id {
154 self.id = Some(id.clone());
155 }
156 self.classes.extend(other.classes.iter().cloned());
157 self.attributes.extend(other.attributes.iter().cloned());
158 self.pseudo_classes
159 .extend(other.pseudo_classes.iter().cloned());
160 if let Some(pe) = &other.pseudo_element {
161 self.pseudo_element = Some(pe.clone());
162 }
163 }
164}
165
166#[derive(Debug, Clone, PartialEq, Eq, Hash)]
168pub enum PseudoClass {
169 Simple(String),
171 SelectorList {
173 name: String,
175 selectors: Vec<ComplexSelector>,
177 },
178 Nth {
180 name: String,
182 a: i32,
184 b: i32,
186 },
187}
188
189#[derive(Debug, Clone, PartialEq, Eq, Hash)]
191pub struct AttributeSelector {
192 pub name: String,
194 pub operator: AttributeSelectorOperator,
196 pub value: Option<String>,
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
202pub enum AttributeSelectorOperator {
203 Exists,
205 Equals,
207 Includes,
209 DashMatch,
211 Prefix,
213 Suffix,
215 Substring,
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
222pub enum Combinator {
223 Descendant,
225 Child,
227 NextSibling,
229 SubsequentSibling,
231}
232
233#[derive(Debug, Clone, PartialEq, Eq, Hash)]
234pub struct SelectorPart {
235 pub selector: Selector,
237
238 pub combinator: Option<Combinator>,
243}
244
245#[derive(Debug, Clone, PartialEq, Eq, Hash)]
262pub struct ComplexSelector {
263 pub parts: Vec<SelectorPart>,
264}
265
266impl ComplexSelector {
267 pub fn empty() -> Self {
268 Self { parts: Vec::new() }
269 }
270
271 pub fn nest(&self, child: &Self) -> Self {
272 if self.parts.is_empty() {
273 return child.clone();
274 }
275 if child.parts.is_empty() {
276 return self.clone();
277 }
278
279 let has_nesting = child.parts.iter().any(|p| p.selector.is_nesting);
280
281 if !has_nesting {
282 let mut parts = child.parts.clone();
284 parts
285 .last_mut()
286 .expect("child selector is known to be non-empty")
287 .combinator = Some(Combinator::Descendant);
288 parts.extend(self.parts.iter().cloned());
289 return Self { parts };
290 }
291
292 let mut result_parts: Vec<SelectorPart> = Vec::new();
294
295 for child_part in &child.parts {
296 if child_part.selector.is_nesting {
297 if child_part.selector.is_compound() {
298 let mut merged = self.parts[0].selector.clone();
302 merged.merge_from(&child_part.selector);
303 merged.is_nesting = false;
304
305 let combinator = child_part.combinator.or(self.parts[0].combinator);
306
307 result_parts.push(SelectorPart {
308 selector: merged,
309 combinator,
310 });
311
312 for parent_part in self.parts.iter().skip(1) {
313 result_parts.push(parent_part.clone());
314 }
315 } else {
316 let child_combinator = child_part.combinator;
320 let parent_len = self.parts.len();
321 for (i, parent_part) in self.parts.iter().enumerate() {
322 let mut part = parent_part.clone();
323 if i == parent_len - 1 {
324 part.combinator = child_combinator;
325 }
326 result_parts.push(part);
327 }
328 }
329 } else {
330 result_parts.push(child_part.clone());
331 }
332 }
333
334 Self {
335 parts: result_parts,
336 }
337 }
338}
339
340fn parse_an_plus_b(tokens: &[Token]) -> Option<(i32, i32)> {
342 let mut expression = String::new();
343 for token in tokens {
344 match token {
345 Token::Whitespace | Token::Comment(_) => {}
346 Token::Ident(value) => expression.push_str(&value.to_ascii_lowercase()),
347 Token::Number(value) if value.fract() == 0.0 => {
348 expression.push_str(&(*value as i32).to_string());
349 }
350 Token::Dimension(value, unit) if value.fract() == 0.0 => {
351 expression.push_str(&(*value as i32).to_string());
352 expression.push_str(&unit.to_ascii_lowercase());
353 }
354 Token::Delim(value @ ('+' | '-')) => expression.push(*value),
355 _ => return None,
356 }
357 }
358
359 match expression.as_str() {
360 "odd" => return Some((2, 1)),
361 "even" => return Some((2, 0)),
362 _ => {}
363 }
364
365 if let Some(n_index) = expression.find('n') {
366 if expression[n_index + 1..].contains('n') {
367 return None;
368 }
369 let coefficient = match &expression[..n_index] {
370 "" | "+" => 1,
371 "-" => -1,
372 value => value.parse().ok()?,
373 };
374 let offset = match &expression[n_index + 1..] {
375 "" => 0,
376 value => value.parse().ok()?,
377 };
378 Some((coefficient, offset))
379 } else {
380 Some((0, expression.parse().ok()?))
381 }
382}
383
384#[derive(Clone)]
386pub struct Parser<'a> {
387 tokenizer: Tokenizer<'a>,
389
390 brace_depth: usize,
392
393 lookahead: VecDeque<Token>,
397}
398
399#[derive(Debug, Clone, PartialEq, Eq)]
401pub enum ParserErrorKind {
402 UnexpectedToken {
404 expected: &'static str,
405 found: String, },
407
408 UnexpectedEOF,
410
411 InvalidSyntax,
413
414 MismatchedDelimiter { expected: char, found: char },
416}
417
418impl fmt::Display for ParserErrorKind {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 write!(f, "{:?}", self)
421 }
422}
423
424#[derive(Debug, Clone, PartialEq, Eq)]
426pub struct ParserError {
427 pub kind: ParserErrorKind,
429 pub context: Vec<String>,
431}
432
433impl ParserError {
434 pub fn with_context(mut self, ctx: impl Into<String>) -> Self {
435 self.context.push(ctx.into());
436 self
437 }
438}
439
440impl fmt::Display for ParserError {
441 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
442 let mut ctx = self.context.clone();
443 ctx.reverse();
444 write!(
445 f,
446 "CssParserError: {}, (Context:[{}])",
447 self.kind,
448 ctx.join(" <-")
449 )
450 }
451}
452
453impl std::error::Error for ParserError {}
454
455pub type ParseResult<T> = Result<T, ParserError>;
457
458impl<'a> Parser<'a> {
459 pub fn new(input: &'a str) -> Self {
461 Self {
462 tokenizer: Tokenizer::new(input),
463 brace_depth: 0,
464 lookahead: VecDeque::new(),
465 }
466 }
467
468 fn ensure_lookahead(&mut self, n: usize) {
469 while self.lookahead.len() <= n {
470 let tok = self.tokenizer.next_token();
471 self.lookahead.push_back(tok);
472 }
473 }
474
475 fn peek_next_token(&mut self, cursor_size: usize) -> &Token {
476 self.ensure_lookahead(cursor_size);
477 &self.lookahead[cursor_size]
478 }
479
480 fn peek_token(&mut self) -> &Token {
482 self.peek_next_token(0)
483 }
484
485 pub fn parse_declarations(&mut self) -> ParseResult<Vec<CssNode>> {
491 self.parse_declaration_and_nested_rule_list()
492 }
493
494 fn consume_token(&mut self) -> Token {
495 if let Some(tok) = self.lookahead.pop_front() {
496 tok
497 } else {
498 self.tokenizer.next_token()
499 }
500 }
501
502 pub fn parse(&mut self) -> ParseResult<CssNode> {
512 let mut stylesheet = CssNode {
513 node: CssNodeType::Stylesheet,
514 children: vec![],
515 };
516
517 loop {
518 let token = self.peek_token().clone();
519
520 match token {
521 Token::EOF => break,
522 Token::Whitespace | Token::Comment(_) => {
523 self.consume_token();
524 }
525 Token::AtKeyword(_) => {
526 let node = self
527 .parse_at_rule()
528 .map_err(|e| e.with_context("parse: failed to parse at-rule"))?;
529 log::debug!(target: "CssParser", "AtRule parsed: {:?}", node);
530 stylesheet.children.push(node);
531 }
532 _ => {
533 let node = self
534 .parse_rule()
535 .map_err(|e| e.with_context("parse: failed to parse rule"))?;
536 log::debug!(target: "CssParser", "Rule parsed: {:?}", node);
537 stylesheet.children.push(node);
538 }
539 }
540 }
541
542 Ok(stylesheet)
543 }
544
545 pub fn parse_lossy(&mut self) -> CssNode {
554 let mut stylesheet = CssNode {
555 node: CssNodeType::Stylesheet,
556 children: vec![],
557 };
558
559 loop {
560 let token = self.peek_token().clone();
561 let checkpoint = self.clone();
562 match token {
563 Token::EOF => break,
564 Token::Whitespace | Token::Comment(_) => {
565 self.consume_token();
566 }
567 Token::AtKeyword(_) => match self.parse_at_rule() {
568 Ok(node) => stylesheet.children.push(node),
569 Err(error) => {
570 log::warn!(
571 target: "CssParser",
572 "Skipping unsupported at-rule: {error}"
573 );
574 *self = checkpoint;
575 self.recover_top_level_item();
576 }
577 },
578 _ => match self.parse_rule() {
579 Ok(node) => stylesheet.children.push(node),
580 Err(error) => {
581 log::warn!(
582 target: "CssParser",
583 "Skipping unsupported CSS rule: {error}"
584 );
585 *self = checkpoint;
586 self.recover_top_level_item();
587 }
588 },
589 }
590 }
591
592 stylesheet
593 }
594
595 fn recover_top_level_item(&mut self) {
596 let mut depth = 0_usize;
597 let mut consumed_any = false;
598 loop {
599 match self.peek_token().clone() {
600 Token::EOF => break,
601 Token::AtKeyword(_) if depth == 0 && consumed_any => break,
606 Token::Delim('{') => {
607 depth += 1;
608 self.consume_token();
609 consumed_any = true;
610 }
611 Token::Delim('}') => {
612 self.consume_token();
613 if depth <= 1 {
614 break;
615 }
616 depth -= 1;
617 consumed_any = true;
618 }
619 Token::Delim(';') if depth == 0 => {
620 self.consume_token();
621 break;
622 }
623 _ => {
624 self.consume_token();
625 consumed_any = true;
626 }
627 }
628 }
629 }
630
631 fn parse_at_rule(&mut self) -> ParseResult<CssNode> {
632 let at_name = if let Token::AtKeyword(name) = self.consume_token() {
634 name
635 } else {
636 return Err(ParserError {
637 kind: ParserErrorKind::UnexpectedToken {
638 expected: "@keyword",
639 found: format!("{:?}", self.peek_token()),
640 },
641 context: vec![],
642 });
643 };
644
645 let mut prelude = vec![];
647 let mut paren_depth = 0;
648
649 loop {
650 match self.peek_token() {
651 Token::Delim('{') if paren_depth == 0 => break,
652 Token::Delim(';') if paren_depth == 0 => break,
653 Token::Delim('(') => {
654 paren_depth += 1;
655 prelude.push(self.consume_token());
656 }
657 Token::Delim(')') => {
658 paren_depth -= 1;
659 prelude.push(self.consume_token());
660 }
661 Token::EOF => break,
662 _ => prelude.push(self.consume_token()),
663 }
664 }
665
666 let params = Self::parse_at_query(prelude).map_err(|e| {
668 e.with_context("parse_at_rule: failed to parse params via parse_at_query")
669 })?;
670
671 let children = if self.peek_token() == &Token::Delim('{') {
673 self.consume_token();
674 self.brace_depth += 1;
675
676 let mut children = vec![];
677 while self.peek_token() != &Token::Delim('}') {
678 match self.peek_token() {
679 Token::EOF => {
680 return Err(ParserError {
681 kind: ParserErrorKind::UnexpectedEOF,
682 context: vec![],
683 });
684 }
685 Token::Whitespace => {
686 self.consume_token();
687 }
688 Token::AtKeyword(_) => {
689 let node = self.parse_at_rule().map_err(|e| {
690 e.with_context("parse_at_rule: failed to parse nested at-rule")
691 })?;
692 children.push(node);
693 }
694 _ => {
695 let mut cursor = 0;
696 let mut is_declaration = false;
697
698 loop {
699 match self.peek_next_token(cursor) {
700 Token::Delim('{') => {
701 break;
702 }
703 Token::Delim('}') => {
704 is_declaration = true;
705 break;
706 }
707 Token::EOF => {
708 return Err(ParserError {
709 kind: ParserErrorKind::UnexpectedEOF,
710 context: vec![],
711 });
712 }
713 _ => {}
714 }
715 cursor += 1;
716 }
717
718 let nodes = if is_declaration {
719 self.parse_declaration_and_nested_rule_list().map_err(|e| {
720 e.with_context(
721 "parse_at_rule: failed to parse declaration in block",
722 )
723 })?
724 } else {
725 vec![self.parse_rule().map_err(|e| {
726 e.with_context("parse_at_rule: failed to parse rule in block")
727 })?]
728 };
729
730 children.extend(nodes);
731 }
732 }
733 }
734
735 self.consume_token(); self.brace_depth -= 1;
737 children
738 } else {
739 if self.consume_token() != Token::Delim(';') {
740 return Err(ParserError {
741 kind: ParserErrorKind::UnexpectedToken {
742 expected: ";",
743 found: format!("{:?}", self.peek_token()),
744 },
745 context: vec![],
746 });
747 }
748 vec![]
749 };
750
751 Ok(CssNode {
752 node: CssNodeType::AtRule {
753 name: at_name,
754 params,
755 },
756 children,
757 })
758 }
759
760 fn parse_at_query(tokens: Vec<Token>) -> ParseResult<AtQuery> {
761 let mut cursor = 0;
762 let items = Self::parse_at_query_list(&tokens, &mut cursor)?;
763 Ok(AtQuery::Group(items))
764 }
765
766 fn parse_at_query_list(tokens: &[Token], cursor: &mut usize) -> ParseResult<Vec<AtQuery>> {
767 let mut items = Vec::new();
768
769 while *cursor < tokens.len() {
770 match &tokens[*cursor] {
771 Token::Whitespace => {
772 *cursor += 1;
773 }
774
775 Token::Delim('(') => {
776 *cursor += 1;
777 let group = Self::parse_at_query_list(tokens, cursor)?;
778 items.push(AtQuery::Group(group));
779 }
780
781 Token::Delim(')') => {
782 *cursor += 1;
783 break;
784 }
785
786 Token::Ident(_) | Token::Number(_) | Token::Dimension(_, _) => {
787 items.push(Self::parse_at_query_item(tokens, cursor)?);
788 }
789
790 Token::Delim(',') => {
791 items.push(AtQuery::Keyword(",".into()));
792 *cursor += 1;
793 }
794
795 _ => {
796 *cursor += 1;
797 }
798 }
799 }
800
801 Ok(items)
802 }
803
804 fn parse_at_query_item(tokens: &[Token], cursor: &mut usize) -> ParseResult<AtQuery> {
805 let start = *cursor;
806
807 if let Some(range) = Self::parse_at_query_range(tokens, cursor)? {
809 return Ok(range);
810 }
811
812 *cursor = start;
813
814 let name = match &tokens[*cursor] {
815 Token::Ident(s) => s.clone(),
816 _ => {
817 return Err(ParserError {
818 kind: ParserErrorKind::UnexpectedToken {
819 expected: "Ident(_)",
820 found: format!("{:?}", tokens[*cursor]),
821 },
822 context: vec![],
823 });
824 }
825 };
826 *cursor += 1;
827
828 let mut colon = *cursor;
829 while matches!(
830 tokens.get(colon),
831 Some(Token::Whitespace | Token::Comment(_))
832 ) {
833 colon += 1;
834 }
835 if matches!(tokens.get(colon), Some(Token::Delim(':'))) {
836 *cursor = colon + 1;
837 let value = Self::parse_at_query_value(tokens, cursor)?;
838 Ok(AtQuery::Condition { name, value })
839 } else {
840 Ok(AtQuery::Keyword(name))
841 }
842 }
843
844 fn parse_at_query_range(tokens: &[Token], cursor: &mut usize) -> ParseResult<Option<AtQuery>> {
845 let start = *cursor;
846
847 if let Token::Ident(name) = tokens.get(*cursor).cloned().unwrap() {
849 *cursor += 1;
850 Self::skip_at_query_whitespace(tokens, cursor);
851
852 if let Some(operator) = Self::parse_range_operator(tokens, cursor) {
853 Self::skip_at_query_whitespace(tokens, cursor);
854
855 if let Some(value) = Self::try_parse_at_query_value(tokens, cursor)? {
856 return Ok(Some(AtQuery::Range {
857 left: None,
858 name,
859 right: Some((operator, value)),
860 }));
861 }
862 }
863 }
864
865 *cursor = start;
866
867 let Some(left) = Self::try_parse_at_query_value(tokens, cursor)? else {
869 return Ok(None);
870 };
871
872 Self::skip_at_query_whitespace(tokens, cursor);
873
874 let Some(left_operator) = Self::parse_range_operator(tokens, cursor) else {
875 *cursor = start;
876 return Ok(None);
877 };
878
879 Self::skip_at_query_whitespace(tokens, cursor);
880
881 let name = match tokens.get(*cursor) {
882 Some(Token::Ident(name)) => name.clone(),
883 _ => {
884 *cursor = start;
885 return Ok(None);
886 }
887 };
888 *cursor += 1;
889
890 Self::skip_at_query_whitespace(tokens, cursor);
891
892 let right = if let Some(operator) = Self::parse_range_operator(tokens, cursor) {
894 Self::skip_at_query_whitespace(tokens, cursor);
895
896 let Some(value) = Self::try_parse_at_query_value(tokens, cursor)? else {
897 *cursor = start;
898 return Ok(None);
899 };
900
901 Some((operator, value))
902 } else {
903 None
904 };
905
906 Ok(Some(AtQuery::Range {
907 left: Some((left, left_operator)),
908 name,
909 right,
910 }))
911 }
912
913 fn parse_range_operator(tokens: &[Token], cursor: &mut usize) -> Option<RangeOperator> {
914 match tokens.get(*cursor) {
915 Some(Token::Delim('<')) => {
916 if matches!(tokens.get(*cursor + 1), Some(Token::Delim('='))) {
917 *cursor += 2;
918 Some(RangeOperator::LessEqual)
919 } else {
920 *cursor += 1;
921 Some(RangeOperator::Less)
922 }
923 }
924
925 Some(Token::Delim('>')) => {
926 if matches!(tokens.get(*cursor + 1), Some(Token::Delim('='))) {
927 *cursor += 2;
928 Some(RangeOperator::GreaterEqual)
929 } else {
930 *cursor += 1;
931 Some(RangeOperator::Greater)
932 }
933 }
934
935 Some(Token::Delim('=')) => {
936 *cursor += 1;
937 Some(RangeOperator::Equal)
938 }
939
940 _ => None,
941 }
942 }
943
944 fn skip_at_query_whitespace(tokens: &[Token], cursor: &mut usize) {
945 while matches!(
946 tokens.get(*cursor),
947 Some(Token::Whitespace | Token::Comment(_))
948 ) {
949 *cursor += 1;
950 }
951 }
952
953 fn try_parse_at_query_value(
954 tokens: &[Token],
955 cursor: &mut usize,
956 ) -> ParseResult<Option<CssValue>> {
957 let start = *cursor;
958
959 match tokens.get(*cursor) {
960 Some(Token::Number(_)) | Some(Token::Dimension(_, _)) | Some(Token::Function(_)) => {
961 match Self::parse_at_query_value(tokens, cursor) {
962 Ok(value) => Ok(Some(value)),
963 Err(_) => {
964 *cursor = start;
965 Ok(None)
966 }
967 }
968 }
969
970 _ => Ok(None),
971 }
972 }
973
974 fn parse_at_query_value(tokens: &[Token], cursor: &mut usize) -> ParseResult<CssValue> {
975 let mut buf = Vec::new();
976 let mut paren_depth = 0;
977
978 while *cursor < tokens.len() {
979 match &tokens[*cursor] {
980 Token::Delim('(') => {
981 paren_depth += 1;
982 buf.push(tokens[*cursor].clone());
983 *cursor += 1;
984 }
985 Token::Delim(')') if paren_depth == 0 => break,
986 Token::Delim(')') => {
987 paren_depth -= 1;
988 buf.push(tokens[*cursor].clone());
989 *cursor += 1;
990 }
991 _ => {
992 buf.push(tokens[*cursor].clone());
993 *cursor += 1;
994 }
995 }
996 }
997
998 Self::parse_tokens_to_css_value(buf)
999 }
1000
1001 fn parse_rule(&mut self) -> ParseResult<CssNode> {
1005 let selectors = self.parse_selector_list();
1007
1008 match self.consume_token() {
1010 Token::Delim('{') => self.brace_depth += 1,
1011 token => {
1012 return Err(ParserError {
1013 kind: ParserErrorKind::UnexpectedToken {
1014 expected: "{",
1015 found: format!("{:?}", token),
1016 },
1017 context: vec![format!(
1018 "While parsing rule with selectors: {}",
1019 selectors
1020 .iter()
1021 .map(|s| format!("{:?}", s))
1022 .collect::<Vec<_>>()
1023 .join(", ")
1024 )],
1025 });
1026 }
1027 }
1028
1029 let mut children = vec![];
1031 loop {
1032 let token = self.peek_token().clone();
1033 match token {
1034 Token::Delim('}') => {
1035 self.consume_token();
1036 self.brace_depth -= 1;
1037 break;
1038 }
1039 Token::EOF => {
1040 return Err(ParserError {
1041 kind: ParserErrorKind::UnexpectedEOF,
1042 context: vec![],
1043 });
1044 }
1045 _ => {
1046 let mut child = self.parse_declaration_and_nested_rule_list().map_err(|e| {
1047 e.with_context("parse_rule: failed to parse declaration list")
1048 })?;
1049 children.append(&mut child);
1050 }
1051 }
1052 }
1053
1054 Ok(CssNode {
1055 node: CssNodeType::Rule { selectors },
1056 children,
1057 })
1058 }
1059
1060 fn parse_selector_list(&mut self) -> Vec<ComplexSelector> {
1064 self.parse_selector_list_until(None)
1065 }
1066
1067 fn parse_selector_list_until(&mut self, terminator: Option<char>) -> Vec<ComplexSelector> {
1070 let mut selectors = vec![];
1071 let mut parts = vec![];
1072
1073 let mut current_selector: Option<Selector> = None;
1074 let mut current_combinator: Option<Combinator> = None;
1075
1076 loop {
1077 let token = self.peek_token().clone();
1078 match token {
1079 Token::Ident(name) => {
1080 let sel = current_selector.get_or_insert_with(|| Selector {
1081 is_nesting: false,
1082 tag: None,
1083 id: None,
1084 classes: vec![],
1085 attributes: vec![],
1086 pseudo_classes: vec![],
1087 pseudo_element: None,
1088 });
1089
1090 if sel.tag.is_none() {
1091 sel.tag = Some(name);
1092 }
1093
1094 self.consume_token();
1095 }
1096
1097 Token::Hash(id) => {
1098 let sel = current_selector.get_or_insert_with(|| Selector {
1099 is_nesting: false,
1100 tag: None,
1101 id: None,
1102 classes: vec![],
1103 attributes: vec![],
1104 pseudo_classes: vec![],
1105 pseudo_element: None,
1106 });
1107 sel.id = Some(id);
1108 self.consume_token();
1109 }
1110
1111 Token::Delim('.') => {
1112 self.consume_token();
1113 if let Token::Ident(class) = self.consume_token() {
1114 let sel = current_selector.get_or_insert_with(|| Selector {
1115 is_nesting: false,
1116 tag: None,
1117 id: None,
1118 classes: vec![],
1119 attributes: vec![],
1120 pseudo_classes: vec![],
1121 pseudo_element: None,
1122 });
1123 sel.classes.push(class);
1124 }
1125 }
1126
1127 Token::Delim(':') => {
1128 self.consume_token();
1129 if self.peek_token() == &Token::Delim(':') {
1130 self.consume_token();
1132 if let Token::Ident(name) = self.consume_token() {
1133 let sel = current_selector.get_or_insert_with(|| Selector {
1134 is_nesting: false,
1135 tag: None,
1136 id: None,
1137 classes: vec![],
1138 attributes: vec![],
1139 pseudo_classes: vec![],
1140 pseudo_element: None,
1141 });
1142 sel.pseudo_element = Some(name);
1143 }
1144 } else {
1145 let pseudo_class = match self.consume_token() {
1146 Token::Ident(name) => Some(PseudoClass::Simple(name)),
1147 Token::Function(name) => {
1148 if self.peek_token() == &Token::Delim('(') {
1149 self.consume_token();
1150 }
1151 let lower_name = name.to_ascii_lowercase();
1152 let pseudo = match lower_name.as_str() {
1153 "is" | "where" | "not" => PseudoClass::SelectorList {
1154 name: lower_name,
1155 selectors: self.parse_selector_list_until(Some(')')),
1156 },
1157 "nth-child" | "nth-last-child" | "nth-of-type"
1158 | "nth-last-of-type" => {
1159 let tokens = self.consume_until_closing_parenthesis();
1160 let (a, b) =
1161 parse_an_plus_b(&tokens).unwrap_or((0, i32::MIN));
1162 PseudoClass::Nth {
1163 name: lower_name,
1164 a,
1165 b,
1166 }
1167 }
1168 _ => {
1169 self.consume_until_closing_parenthesis();
1170 PseudoClass::SelectorList {
1171 name: lower_name,
1172 selectors: Vec::new(),
1173 }
1174 }
1175 };
1176 if self.peek_token() == &Token::Delim(')') {
1177 self.consume_token();
1178 }
1179 Some(pseudo)
1180 }
1181 _ => None,
1182 };
1183 if let Some(pseudo_class) = pseudo_class {
1184 let sel = current_selector.get_or_insert_with(|| Selector {
1185 is_nesting: false,
1186 tag: None,
1187 id: None,
1188 classes: vec![],
1189 attributes: vec![],
1190 pseudo_classes: vec![],
1191 pseudo_element: None,
1192 });
1193 sel.pseudo_classes.push(pseudo_class);
1194 }
1195 }
1196 }
1197
1198 Token::Delim('[') => {
1199 self.consume_token();
1200 while matches!(self.peek_token(), Token::Whitespace | Token::Comment(_)) {
1201 self.consume_token();
1202 }
1203
1204 let name = match self.consume_token() {
1205 Token::Ident(name) => name,
1206 _ => continue,
1207 };
1208
1209 while matches!(self.peek_token(), Token::Whitespace | Token::Comment(_)) {
1210 self.consume_token();
1211 }
1212
1213 let operator = match self.peek_token() {
1214 Token::Delim('=') => {
1215 self.consume_token();
1216 AttributeSelectorOperator::Equals
1217 }
1218 Token::Delim('~')
1219 | Token::Delim('|')
1220 | Token::Delim('^')
1221 | Token::Delim('$')
1222 | Token::Delim('*') => {
1223 let operator = match self.consume_token() {
1224 Token::Delim(c) => c,
1225 _ => unreachable!(),
1226 };
1227
1228 if self.peek_token() != &Token::Delim('=') {
1229 continue;
1230 }
1231 self.consume_token();
1232
1233 match operator {
1234 '~' => AttributeSelectorOperator::Includes,
1235 '|' => AttributeSelectorOperator::DashMatch,
1236 '^' => AttributeSelectorOperator::Prefix,
1237 '$' => AttributeSelectorOperator::Suffix,
1238 '*' => AttributeSelectorOperator::Substring,
1239 _ => unreachable!(),
1240 }
1241 }
1242 _ => AttributeSelectorOperator::Exists,
1243 };
1244
1245 let value = if operator == AttributeSelectorOperator::Exists {
1246 None
1247 } else {
1248 while matches!(self.peek_token(), Token::Whitespace | Token::Comment(_)) {
1249 self.consume_token();
1250 }
1251
1252 match self.consume_token() {
1253 Token::Ident(value) | Token::String(value) => Some(value),
1254 _ => continue,
1255 }
1256 };
1257
1258 while matches!(self.peek_token(), Token::Whitespace | Token::Comment(_)) {
1259 self.consume_token();
1260 }
1261 if self.peek_token() == &Token::Delim(']') {
1262 self.consume_token();
1263 let sel = current_selector.get_or_insert_with(|| Selector {
1264 is_nesting: false,
1265 tag: None,
1266 id: None,
1267 classes: vec![],
1268 attributes: vec![],
1269 pseudo_classes: vec![],
1270 pseudo_element: None,
1271 });
1272 sel.attributes.push(AttributeSelector {
1273 name,
1274 operator,
1275 value,
1276 });
1277 }
1278 }
1279
1280 Token::Whitespace | Token::Comment(_) => {
1281 if let Some(sel) = current_selector.take() {
1283 parts.push(SelectorPart {
1284 selector: sel,
1285 combinator: current_combinator.take(),
1286 });
1287 }
1288 if current_combinator.is_none() {
1289 current_combinator = Some(Combinator::Descendant);
1290 }
1291 self.consume_token();
1292 }
1293
1294 Token::Delim('>') => {
1295 if let Some(sel) = current_selector.take() {
1296 parts.push(SelectorPart {
1297 selector: sel,
1298 combinator: current_combinator.take(),
1299 });
1300 }
1301 current_combinator = Some(Combinator::Child);
1302 self.consume_token();
1303 }
1304
1305 Token::Delim('+') | Token::Delim('~') => {
1306 if let Some(sel) = current_selector.take() {
1307 parts.push(SelectorPart {
1308 selector: sel,
1309 combinator: current_combinator.take(),
1310 });
1311 }
1312 current_combinator = Some(if token == Token::Delim('+') {
1313 Combinator::NextSibling
1314 } else {
1315 Combinator::SubsequentSibling
1316 });
1317 self.consume_token();
1318 }
1319
1320 Token::Delim('*') => {
1321 current_selector.get_or_insert_with(|| Selector {
1322 is_nesting: false,
1323 tag: None,
1324 id: None,
1325 classes: vec![],
1326 attributes: vec![],
1327 pseudo_classes: vec![],
1328 pseudo_element: None,
1329 });
1330 self.consume_token();
1331 }
1332
1333 Token::Delim('&') => {
1334 let sel = current_selector.get_or_insert_with(|| Selector {
1335 is_nesting: false,
1336 tag: None,
1337 id: None,
1338 classes: vec![],
1339 attributes: vec![],
1340 pseudo_classes: vec![],
1341 pseudo_element: None,
1342 });
1343 sel.is_nesting = true;
1344 self.consume_token();
1345 }
1346
1347 Token::Delim(',') => {
1348 if let Some(sel) = current_selector.take() {
1349 parts.push(SelectorPart {
1350 selector: sel,
1351 combinator: current_combinator.take(),
1352 });
1353 }
1354 parts.reverse();
1355 selectors.push(ComplexSelector {
1356 parts: parts.clone(),
1357 });
1358 parts.clear();
1359 current_combinator = None;
1360 self.consume_token();
1361
1362 while matches!(self.peek_token(), Token::Whitespace | Token::Comment(_)) {
1363 self.consume_token();
1364 }
1365 }
1366
1367 Token::Delim(')') if terminator == Some(')') => {
1368 if let Some(sel) = current_selector.take() {
1369 parts.push(SelectorPart {
1370 selector: sel,
1371 combinator: current_combinator.take(),
1372 });
1373 }
1374 if !parts.is_empty() {
1375 parts.reverse();
1376 selectors.push(ComplexSelector { parts });
1377 }
1378 break;
1379 }
1380
1381 Token::Delim('{') | Token::EOF => {
1382 if let Some(sel) = current_selector.take() {
1383 parts.push(SelectorPart {
1384 selector: sel,
1385 combinator: current_combinator.take(),
1386 });
1387 }
1388 if !parts.is_empty() {
1389 parts.reverse();
1390 selectors.push(ComplexSelector { parts });
1391 }
1392 break;
1393 }
1394
1395 Token::AtKeyword(_) => break,
1399
1400 _ => {
1401 self.consume_token();
1402 }
1403 }
1404 }
1405
1406 selectors
1407 }
1408
1409 fn consume_until_closing_parenthesis(&mut self) -> Vec<Token> {
1411 let mut tokens = Vec::new();
1412 let mut depth = 0;
1413 loop {
1414 match self.peek_token() {
1415 Token::EOF => break,
1416 Token::Delim(')') if depth == 0 => break,
1417 Token::Delim('(') => {
1418 depth += 1;
1419 tokens.push(self.consume_token());
1420 }
1421 Token::Delim(')') => {
1422 depth -= 1;
1423 tokens.push(self.consume_token());
1424 }
1425 _ => tokens.push(self.consume_token()),
1426 }
1427 }
1428 tokens
1429 }
1430
1431 fn parse_declaration_and_nested_rule_list(&mut self) -> ParseResult<Vec<CssNode>> {
1433 let mut children = vec![];
1434 let mut parsing_name = true;
1435 let mut name = String::new();
1436 let mut value_tokens = vec![];
1437
1438 loop {
1439 let mut cursor = 0;
1440
1441 loop {
1442 let token = self.peek_next_token(cursor);
1443
1444 match token {
1445 Token::Delim(':') if parsing_name => {
1446 for _ in 0..cursor {
1447 if let Token::Ident(s) = self.consume_token() {
1448 name.push_str(&s);
1449 }
1450 }
1451
1452 self.consume_token(); parsing_name = false;
1454 break;
1455 }
1456 Token::Delim(';') if !parsing_name => {
1457 for _ in 0..cursor {
1458 value_tokens.push(self.consume_token());
1459 }
1460
1461 self.consume_token(); children.push(CssNode {
1463 node: CssNodeType::Declaration {
1464 name: std::mem::take(&mut name),
1465 value: Self::parse_tokens_to_css_value(std::mem::take(
1466 &mut value_tokens,
1467 ))
1468 .map_err(|e| {
1469 e.with_context(
1470 "parse_declaration: failed to parse declaration value list",
1471 )
1472 })?,
1473 },
1474 children: vec![],
1475 });
1476
1477 parsing_name = true;
1478 break;
1479 }
1480 Token::Delim('{') => {
1481 children.push(self.parse_rule()?);
1482 cursor = 0;
1483 }
1484 Token::Delim('}') | Token::EOF => {
1485 if !parsing_name && !name.is_empty() {
1486 for _ in 0..cursor {
1487 value_tokens.push(self.consume_token());
1488 }
1489
1490 children.push(CssNode {
1491 node: CssNodeType::Declaration {
1492 name: std::mem::take(&mut name),
1493 value: Self::parse_tokens_to_css_value(std::mem::take(
1494 &mut value_tokens,
1495 ))?,
1496 },
1497 children: vec![],
1498 });
1499 } else {
1500 for _ in 0..cursor {
1501 self.consume_token();
1503 }
1504 }
1505
1506 break;
1507 }
1508 Token::Ident(s) if parsing_name => {
1509 if cursor == 0 {
1510 name.push_str(s);
1511 self.consume_token();
1512 break;
1513 }
1514
1515 cursor += 1;
1516 }
1517 _ => {
1518 cursor += 1;
1519 }
1520 }
1521 }
1522
1523 if matches!(self.peek_next_token(0), Token::Delim('}') | Token::EOF) {
1524 break;
1525 }
1526 }
1527
1528 Ok(children)
1529 }
1530
1531 fn parse_function_arguments(tokens: Vec<Token>) -> ParseResult<Vec<Vec<CssValue>>> {
1540 let mut arguments: Vec<Vec<Token>> = Vec::new();
1542 let mut current: Vec<Token> = Vec::new();
1543 let mut depth = 0usize;
1544
1545 for token in tokens {
1546 match token {
1547 Token::Delim('(') => {
1548 depth += 1;
1549 current.push(token);
1550 }
1551 Token::Delim(')') => {
1552 depth = depth.saturating_sub(1);
1553 current.push(token);
1554 }
1555 Token::Delim(',') if depth == 0 => {
1556 arguments.push(std::mem::take(&mut current));
1557 }
1558 _ => current.push(token),
1559 }
1560 }
1561 if !current.is_empty() || arguments.is_empty() {
1562 arguments.push(current);
1563 }
1564
1565 let mut result = Vec::new();
1567 for argument in arguments {
1568 let mut components = Vec::new();
1569 let mut component_tokens: Vec<Token> = Vec::new();
1570 let mut component_depth = 0usize;
1571
1572 for token in argument {
1573 match token {
1574 Token::Whitespace if component_depth == 0 => {
1575 if !component_tokens.is_empty() {
1576 let value = Self::parse_tokens_to_css_value(std::mem::take(
1577 &mut component_tokens,
1578 ))?;
1579 components.push(value);
1580 }
1581 }
1582 Token::Delim('(') => {
1583 component_depth += 1;
1584 component_tokens.push(token);
1585 }
1586 Token::Delim(')') => {
1587 component_depth = component_depth.saturating_sub(1);
1588 component_tokens.push(token);
1589 }
1590 _ => component_tokens.push(token),
1591 }
1592 }
1593 if !component_tokens.is_empty() {
1594 let value = Self::parse_tokens_to_css_value(component_tokens)?;
1595 components.push(value);
1596 }
1597 result.push(components);
1598 }
1599
1600 Ok(result)
1601 }
1602
1603 pub fn parse_tokens_to_css_value(tokens: Vec<Token>) -> ParseResult<CssValue> {
1604 let mut values = vec![];
1605 let mut iter = tokens.into_iter().peekable();
1606
1607 while let Some(token) = iter.next() {
1608 log::debug!(target: "CssParser", "parse_tokens_to_css_value: token={:?}", token);
1609
1610 match token {
1611 Token::Ident(s) => values.push(CssValue::Keyword(s.into())),
1612
1613 Token::Delim(',') => {
1614 continue;
1616 }
1617
1618 Token::Delim('(') | Token::Delim(')') => {
1619 continue;
1621 }
1622
1623 Token::Delim(c) => {
1624 let mut s = [0_u8; 4];
1625 let s = c.encode_utf8(&mut s);
1626
1627 values.push(CssValue::Keyword(s.into()));
1628 }
1629
1630 Token::Number(n) => values.push(CssValue::Number(n)),
1631
1632 Token::String(s) => values.push(CssValue::String(s)),
1633
1634 Token::Url(raw) => values.push(CssValue::Function(
1635 "url".to_string(),
1636 vec![vec![CssValue::String(raw)]],
1637 )),
1638
1639 Token::Dimension(value, unit) => {
1640 let unit = match unit.as_str() {
1641 "px" => Unit::Px,
1642 "cm" => Unit::Cm,
1643 "mm" => Unit::Mm,
1644 "in" => Unit::In,
1645 "pt" => Unit::Pt,
1646 "pc" => Unit::Pc,
1647 "em" => Unit::Em,
1648 "rem" => Unit::Rem,
1649 "%" => Unit::Percent,
1650 "vw" => Unit::Vw,
1651 "vh" => Unit::Vh,
1652 "vmin" => Unit::Vmin,
1653 "vmax" => Unit::Vmax,
1654 "deg" => Unit::Deg,
1655 "fr" => Unit::Fr,
1656 _ => Unit::Unknown,
1657 };
1658 values.push(CssValue::Length(value, unit));
1659 }
1660
1661 Token::Hash(s) => values.push(CssValue::Color(s)),
1662
1663 Token::Function(name) => {
1664 let mut depth = 0;
1666 let mut func_tokens = vec![];
1667
1668 for tok in iter.by_ref() {
1669 match &tok {
1670 Token::Delim('(') => {
1671 depth += 1;
1672 if depth > 1 {
1673 func_tokens.push(tok);
1674 }
1675 }
1676 Token::Delim(')') => {
1677 depth -= 1;
1678 if depth == 0 {
1679 break;
1680 }
1681 func_tokens.push(tok);
1682 }
1683 _ => func_tokens.push(tok),
1684 }
1685 }
1686
1687 let args = Self::parse_function_arguments(func_tokens)
1688 .map_err(|e| e.with_context("parse function args"))?;
1689
1690 values.push(CssValue::Function(name, args));
1691 }
1692
1693 _ => continue,
1694 }
1695 }
1696
1697 Ok(match values.len() {
1699 0 => CssValue::Keyword(CssIdent::new_static("")),
1700 1 => values.remove(0),
1701 _ => CssValue::List(values),
1702 })
1703 }
1704}
1705
1706impl fmt::Display for CssNode {
1708 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1709 fmt_tree_node(self, f, &[])
1710 }
1711}
1712
1713fn fmt_tree_node(
1715 node: &CssNode,
1716 f: &mut fmt::Formatter<'_>,
1717 ancestors_last: &[bool],
1718) -> fmt::Result {
1719 let is_last = *ancestors_last.last().unwrap_or(&true);
1720 let connector = if ancestors_last.is_empty() {
1721 ""
1722 } else if is_last {
1723 "└── "
1724 } else {
1725 "├── "
1726 };
1727
1728 let mut prefix = String::new();
1729 for &ancestor_last in &ancestors_last[..ancestors_last.len().saturating_sub(1)] {
1730 prefix.push_str(if ancestor_last { " " } else { "│ " });
1731 }
1732
1733 writeln!(f, "{}{}{:?}", prefix, connector, node.node())?;
1734
1735 let child_count = node.children().len();
1736 for (i, child) in node.children().iter().enumerate() {
1737 let child_is_last = i == child_count - 1;
1738 let mut new_ancestors = ancestors_last.to_vec();
1739 new_ancestors.push(child_is_last);
1740 fmt_tree_node(child, f, &new_ancestors)?;
1741 }
1742
1743 Ok(())
1744}
1745
1746impl std::fmt::Display for Combinator {
1747 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1748 match self {
1749 Combinator::Descendant => f.write_str(" "),
1750 Combinator::Child => f.write_str(" > "),
1751 Combinator::NextSibling => f.write_str(" + "),
1752 Combinator::SubsequentSibling => f.write_str(" ~ "),
1753 }
1754 }
1755}
1756
1757fn write_an_plus_b(f: &mut std::fmt::Formatter<'_>, a: i32, b: i32) -> std::fmt::Result {
1760 if a == 0 {
1761 return write!(f, "{b}");
1762 }
1763 match a {
1764 1 => f.write_str("n")?,
1765 -1 => f.write_str("-n")?,
1766 _ => write!(f, "{a}n")?,
1767 }
1768 if b > 0 {
1769 write!(f, "+{b}")
1770 } else if b < 0 {
1771 write!(f, "{b}")
1772 } else {
1773 Ok(())
1774 }
1775}
1776
1777impl std::fmt::Display for PseudoClass {
1778 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1779 match self {
1780 PseudoClass::Simple(name) => write!(f, ":{name}"),
1781 PseudoClass::SelectorList { name, selectors } => {
1782 let arguments = selectors
1783 .iter()
1784 .map(ComplexSelector::to_string)
1785 .collect::<Vec<_>>()
1786 .join(", ");
1787 write!(f, ":{name}({arguments})")
1788 }
1789 PseudoClass::Nth { name, a, b } => {
1790 write!(f, ":{name}(")?;
1791 write_an_plus_b(f, *a, *b)?;
1792 f.write_str(")")
1793 }
1794 }
1795 }
1796}
1797
1798impl std::fmt::Display for Selector {
1799 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1800 if self.is_nesting {
1801 f.write_str("&")?;
1802 }
1803 if let Some(tag) = &self.tag {
1804 f.write_str(tag)?;
1805 }
1806 if let Some(id) = &self.id {
1807 write!(f, "#{id}")?;
1808 }
1809 for class in &self.classes {
1810 write!(f, ".{class}")?;
1811 }
1812 for attribute in &self.attributes {
1813 f.write_str("[")?;
1814 f.write_str(&attribute.name)?;
1815 match (&attribute.operator, &attribute.value) {
1816 (AttributeSelectorOperator::Exists, _) => {}
1817 (AttributeSelectorOperator::Equals, Some(value)) => {
1818 write!(f, "=\"{value}\"")?;
1819 }
1820 (AttributeSelectorOperator::Includes, Some(value)) => {
1821 write!(f, "~=\"{value}\"")?;
1822 }
1823 (AttributeSelectorOperator::DashMatch, Some(value)) => {
1824 write!(f, "|=\"{value}\"")?;
1825 }
1826 (AttributeSelectorOperator::Prefix, Some(value)) => {
1827 write!(f, "^=\"{value}\"")?;
1828 }
1829 (AttributeSelectorOperator::Suffix, Some(value)) => {
1830 write!(f, "$=\"{value}\"")?;
1831 }
1832 (AttributeSelectorOperator::Substring, Some(value)) => {
1833 write!(f, "*=\"{value}\"")?;
1834 }
1835
1836 _ => {}
1837 }
1838 f.write_str("]")?;
1839 }
1840 for pseudo_class in &self.pseudo_classes {
1841 write!(f, "{pseudo_class}")?;
1842 }
1843 if let Some(pseudo_element) = &self.pseudo_element {
1844 write!(f, "::{pseudo_element}")?;
1845 }
1846 Ok(())
1847 }
1848}
1849
1850impl std::fmt::Display for ComplexSelector {
1851 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1852 for index in (0..self.parts.len()).rev() {
1857 write!(f, "{}", self.parts[index].selector)?;
1858 if index > 0
1859 && let Some(combinator) = self.parts[index - 1].combinator
1860 {
1861 write!(f, "{combinator}")?;
1862 }
1863 }
1864 Ok(())
1865 }
1866}
1867
1868#[cfg(test)]
1869mod tests {
1870 use super::*;
1871
1872 #[test]
1873 fn complex_selector_display_renders_combinators_and_compounds() {
1874 let stylesheet = Parser::new("div.a > p#x + span:hover { color: red; }")
1875 .parse()
1876 .unwrap();
1877 let CssNodeType::Rule { selectors } = stylesheet.children()[0].node() else {
1878 panic!("expected a rule");
1879 };
1880 assert_eq!(selectors[0].to_string(), "div.a > p#x + span:hover");
1881
1882 let stylesheet = Parser::new("ul li ~ a[rel=\"tag\"] { color: red; }")
1883 .parse()
1884 .unwrap();
1885 let CssNodeType::Rule { selectors } = stylesheet.children()[0].node() else {
1886 panic!("expected a rule");
1887 };
1888 assert_eq!(selectors[0].to_string(), "ul li ~ a[rel=\"tag\"]");
1889 }
1890
1891 #[test]
1892 fn selector_display_renders_nth_arguments() {
1893 let stylesheet = Parser::new("li:nth-child(2n+1) { color: red; }")
1894 .parse()
1895 .unwrap();
1896 let CssNodeType::Rule { selectors } = stylesheet.children()[0].node() else {
1897 panic!("expected a rule");
1898 };
1899 assert_eq!(selectors[0].to_string(), "li:nth-child(2n+1)");
1900 }
1901
1902 #[test]
1903 fn parses_exact_attribute_selector() {
1904 let stylesheet = Parser::new(r#"input[type="hidden"] { display: none; }"#)
1905 .parse()
1906 .unwrap();
1907 let CssNodeType::Rule { selectors } = stylesheet.children()[0].node() else {
1908 panic!("expected CSS rule");
1909 };
1910 let selector = &selectors[0].parts[0].selector;
1911
1912 assert_eq!(selector.tag.as_deref(), Some("input"));
1913 assert_eq!(
1914 selector.attributes,
1915 vec![AttributeSelector {
1916 name: "type".into(),
1917 operator: AttributeSelectorOperator::Equals,
1918 value: Some("hidden".into()),
1919 }]
1920 );
1921 }
1922
1923 #[test]
1924 fn preserves_fractional_grid_units() {
1925 let stylesheet = Parser::new("main { grid-template-columns: 100px 2fr auto; }")
1926 .parse()
1927 .unwrap();
1928 let declaration = stylesheet.children()[0].children()[0].node();
1929 let CssNodeType::Declaration { value, .. } = declaration else {
1930 panic!("expected declaration");
1931 };
1932 assert_eq!(
1933 value,
1934 &CssValue::List(vec![
1935 CssValue::Length(100.0, Unit::Px),
1936 CssValue::Length(2.0, Unit::Fr),
1937 CssValue::Keyword("auto".into()),
1938 ])
1939 );
1940 }
1941
1942 #[test]
1943 fn preserves_grid_functions_and_area_strings() {
1944 let stylesheet = Parser::new(
1945 r#"main {
1946 grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
1947 grid-template-areas: "header header" "sidebar main";
1948 }"#,
1949 )
1950 .parse()
1951 .unwrap();
1952 let declarations = stylesheet.children()[0].children();
1953 let CssNodeType::Declaration { value, .. } = declarations[0].node() else {
1954 panic!("expected declaration");
1955 };
1956 assert_eq!(
1957 value,
1958 &CssValue::Function(
1959 "repeat".into(),
1960 vec![
1961 vec![CssValue::Keyword("auto-fit".into())],
1962 vec![CssValue::Function(
1963 "minmax".into(),
1964 vec![
1965 vec![CssValue::Length(100.0, Unit::Px)],
1966 vec![CssValue::Length(1.0, Unit::Fr)],
1967 ],
1968 )],
1969 ],
1970 )
1971 );
1972 let CssNodeType::Declaration { value, .. } = declarations[1].node() else {
1973 panic!("expected declaration");
1974 };
1975 assert_eq!(
1976 value,
1977 &CssValue::List(vec![
1978 CssValue::String("header header".into()),
1979 CssValue::String("sidebar main".into()),
1980 ])
1981 );
1982 }
1983
1984 #[test]
1985 fn function_arguments_preserve_comma_and_whitespace_boundaries() {
1986 let stylesheet = Parser::new("a { width: foo(a + b, c, d); }")
1987 .parse()
1988 .unwrap();
1989 let declarations = stylesheet.children()[0].children();
1990 let CssNodeType::Declaration { value, .. } = declarations[0].node() else {
1991 panic!("expected declaration");
1992 };
1993 assert_eq!(
1994 value,
1995 &CssValue::Function(
1996 "foo".into(),
1997 vec![
1998 vec![
1999 CssValue::Keyword("a".into()),
2000 CssValue::Keyword("+".into()),
2001 CssValue::Keyword("b".into()),
2002 ],
2003 vec![CssValue::Keyword("c".into())],
2004 vec![CssValue::Keyword("d".into())],
2005 ],
2006 )
2007 );
2008 }
2009
2010 #[test]
2011 fn malformed_function_arguments_are_structurally_distinct() {
2012 let parse = |css: &str| {
2013 let stylesheet = Parser::new(&format!("a {{ width: {css}; }}"))
2014 .parse()
2015 .unwrap();
2016 let declarations = stylesheet.children()[0].children();
2017 let CssNodeType::Declaration { value, .. } = declarations[0].node() else {
2018 panic!("expected declaration");
2019 };
2020 value.clone()
2021 };
2022
2023 let comma_separated = parse("foo(a + b, c, d)");
2025 let whitespace_separated = parse("foo(a, + b c, d)");
2027
2028 assert_ne!(comma_separated, whitespace_separated);
2029 }
2030
2031 #[test]
2032 fn lossy_parser_resumes_after_recovering_a_failed_at_rule() {
2033 let mut parser = Parser::new("@media { @broken } .valid { color: green; }");
2034 let stylesheet = parser.parse_lossy();
2035
2036 assert_eq!(stylesheet.children().len(), 1);
2037 let CssNodeType::Rule { selectors } = stylesheet.children()[0].node() else {
2038 panic!("expected recovered CSS rule");
2039 };
2040 assert_eq!(selectors[0].parts[0].selector.tag.as_deref(), None);
2041 assert_eq!(
2042 selectors[0].parts[0].selector.classes,
2043 vec![String::from("valid")]
2044 );
2045 }
2046
2047 #[test]
2048 fn nesting_without_ampersand_uses_descendant_combinator() {
2049 let stylesheet = Parser::new(".parent { span { color: red; } }")
2050 .parse()
2051 .unwrap();
2052 let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2053 panic!("expected parent rule");
2054 };
2055 let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2056 else {
2057 panic!("expected child rule");
2058 };
2059
2060 assert_eq!(child[0].to_string(), "span");
2062
2063 let resolved = parent[0].nest(&child[0]);
2065 assert_eq!(resolved.to_string(), ".parent span");
2066 }
2067
2068 #[test]
2069 fn nesting_with_standalone_ampersand() {
2070 let stylesheet = Parser::new(".parent { & { color: red; } }")
2071 .parse()
2072 .unwrap();
2073 let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2074 panic!("expected parent rule");
2075 };
2076 let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2077 else {
2078 panic!("expected child rule");
2079 };
2080
2081 assert!(child[0].parts[0].selector.is_nesting);
2082
2083 let resolved = parent[0].nest(&child[0]);
2084 assert_eq!(resolved.to_string(), ".parent");
2085 }
2086
2087 #[test]
2088 fn nesting_with_ampersand_class_compound() {
2089 let stylesheet = Parser::new(".parent { &.highlight { color: red; } }")
2090 .parse()
2091 .unwrap();
2092 let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2093 panic!("expected parent rule");
2094 };
2095 let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2096 else {
2097 panic!("expected child rule");
2098 };
2099
2100 assert!(child[0].parts[0].selector.is_nesting);
2101 assert_eq!(child[0].parts[0].selector.classes, vec!["highlight"]);
2102
2103 let resolved = parent[0].nest(&child[0]);
2104 assert_eq!(resolved.to_string(), ".parent.highlight");
2105 }
2106
2107 #[test]
2108 fn nesting_with_ampersand_at_end_of_compound() {
2109 let stylesheet = Parser::new(".parent { .sidebar& { color: red; } }")
2110 .parse()
2111 .unwrap();
2112 let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2113 panic!("expected parent rule");
2114 };
2115 let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2116 else {
2117 panic!("expected child rule");
2118 };
2119
2120 assert!(child[0].parts[0].selector.is_nesting);
2121 assert_eq!(child[0].parts[0].selector.classes, vec!["sidebar"]);
2122
2123 let resolved = parent[0].nest(&child[0]);
2124 assert_eq!(resolved.to_string(), ".parent.sidebar");
2125 }
2126
2127 #[test]
2128 fn nesting_with_ampersand_and_child_combinator() {
2129 let stylesheet = Parser::new(".parent { & > span { color: red; } }")
2130 .parse()
2131 .unwrap();
2132 let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2133 panic!("expected parent rule");
2134 };
2135 let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2136 else {
2137 panic!("expected child rule");
2138 };
2139
2140 let resolved = parent[0].nest(&child[0]);
2141 assert_eq!(resolved.to_string(), ".parent > span");
2142 }
2143
2144 #[test]
2145 fn nesting_with_multiple_selectors_using_ampersand() {
2146 let stylesheet = Parser::new(".parent { &.a, &.b { color: red; } }")
2147 .parse()
2148 .unwrap();
2149 let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2150 panic!("expected parent rule");
2151 };
2152 let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2153 else {
2154 panic!("expected child rule");
2155 };
2156
2157 assert_eq!(child.len(), 2);
2158 let resolved_a = parent[0].nest(&child[0]);
2159 let resolved_b = parent[0].nest(&child[1]);
2160 assert_eq!(resolved_a.to_string(), ".parent.a");
2161 assert_eq!(resolved_b.to_string(), ".parent.b");
2162 }
2163
2164 #[test]
2165 fn nesting_with_descendant_then_ampersand() {
2166 let stylesheet = Parser::new(".outer { .parent { &.highlight { color: red; } } }")
2167 .parse()
2168 .unwrap();
2169 let CssNodeType::Rule { selectors: outer } = stylesheet.children()[0].node() else {
2170 panic!("expected outer rule");
2171 };
2172 let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].children()[0].node()
2173 else {
2174 panic!("expected parent rule");
2175 };
2176 let CssNodeType::Rule { selectors: child } =
2177 stylesheet.children()[0].children()[0].children()[0].node()
2178 else {
2179 panic!("expected child rule");
2180 };
2181
2182 let resolved_parent = outer[0].nest(&parent[0]);
2184 assert_eq!(resolved_parent.to_string(), ".outer .parent");
2185
2186 let resolved_child = resolved_parent.nest(&child[0]);
2188 assert_eq!(resolved_child.to_string(), ".outer .parent.highlight");
2189 }
2190
2191 #[test]
2192 fn nesting_with_multilevel_ampersand_and_combinator() {
2193 let stylesheet = Parser::new("#id .parent { .a & .b > span { color: red; } }")
2194 .parse()
2195 .unwrap();
2196 let CssNodeType::Rule { selectors: parent } = stylesheet.children()[0].node() else {
2197 panic!("expected parent rule");
2198 };
2199 let CssNodeType::Rule { selectors: child } = stylesheet.children()[0].children()[0].node()
2200 else {
2201 panic!("expected child rule");
2202 };
2203
2204 let resolved = parent[0].nest(&child[0]);
2205 assert_eq!(resolved.to_string(), ".a #id .parent .b > span");
2206 }
2207
2208 #[test]
2209 fn nested_declaration_parsing() {
2210 let stylesheet = Parser::new(".parent { color: red; & { font-size: 14px; } }")
2211 .parse()
2212 .unwrap();
2213 let CssNodeType::Rule { selectors } = stylesheet.children()[0].node() else {
2214 panic!("expected parent rule");
2215 };
2216 assert_eq!(selectors[0].to_string(), ".parent");
2217
2218 let children = stylesheet.children()[0].children();
2219 assert_eq!(children.len(), 2);
2220
2221 let CssNodeType::Declaration { name, .. } = children[0].node() else {
2223 panic!("expected declaration");
2224 };
2225 assert_eq!(name, "color");
2226
2227 let CssNodeType::Rule { selectors: nested } = children[1].node() else {
2229 panic!("expected nested rule");
2230 };
2231 assert!(nested[0].parts[0].selector.is_nesting);
2232 }
2233}