web engine - experimental web browser
at poly1305-h4-fix 1477 lines 46 kB view raw
1//! CSS parser per CSS Syntax Module Level 3 §5. 2//! 3//! Consumes tokens from the tokenizer and produces a structured stylesheet. 4 5use crate::tokenizer::{HashType, NumericType, Token, Tokenizer}; 6 7// --------------------------------------------------------------------------- 8// AST types 9// --------------------------------------------------------------------------- 10 11/// A parsed CSS stylesheet: a list of rules. 12#[derive(Debug, Clone, PartialEq)] 13pub struct Stylesheet { 14 pub rules: Vec<Rule>, 15} 16 17/// A top-level rule: either a style rule or an at-rule. 18#[derive(Debug, Clone, PartialEq)] 19pub enum Rule { 20 Style(StyleRule), 21 Media(MediaRule), 22 Import(ImportRule), 23} 24 25/// A style rule: selector list + declarations. 26#[derive(Debug, Clone, PartialEq)] 27pub struct StyleRule { 28 pub selectors: SelectorList, 29 pub declarations: Vec<Declaration>, 30} 31 32/// A `@media` rule with a prelude (media query text) and nested rules. 33#[derive(Debug, Clone, PartialEq)] 34pub struct MediaRule { 35 pub query: String, 36 pub rules: Vec<Rule>, 37} 38 39/// An `@import` rule with a URL. 40#[derive(Debug, Clone, PartialEq)] 41pub struct ImportRule { 42 pub url: String, 43} 44 45/// A comma-separated list of selectors. 46#[derive(Debug, Clone, PartialEq)] 47pub struct SelectorList { 48 pub selectors: Vec<Selector>, 49} 50 51/// A complex selector: a chain of compound selectors joined by combinators. 52#[derive(Debug, Clone, PartialEq)] 53pub struct Selector { 54 pub components: Vec<SelectorComponent>, 55} 56 57/// Either a compound selector or a combinator between compounds. 58#[derive(Debug, Clone, PartialEq)] 59pub enum SelectorComponent { 60 Compound(CompoundSelector), 61 Combinator(Combinator), 62} 63 64/// A compound selector: a sequence of simple selectors with no combinator. 65#[derive(Debug, Clone, PartialEq)] 66pub struct CompoundSelector { 67 pub simple: Vec<SimpleSelector>, 68} 69 70/// A simple selector. 71#[derive(Debug, Clone, PartialEq)] 72pub enum SimpleSelector { 73 Type(String), 74 Universal, 75 Class(String), 76 Id(String), 77 Attribute(AttributeSelector), 78 PseudoClass(String), 79} 80 81/// An attribute selector. 82#[derive(Debug, Clone, PartialEq)] 83pub struct AttributeSelector { 84 pub name: String, 85 pub op: Option<AttributeOp>, 86 pub value: Option<String>, 87} 88 89/// Attribute matching operator. 90#[derive(Debug, Clone, Copy, PartialEq, Eq)] 91pub enum AttributeOp { 92 /// `=` 93 Exact, 94 /// `~=` 95 Includes, 96 /// `|=` 97 DashMatch, 98 /// `^=` 99 Prefix, 100 /// `$=` 101 Suffix, 102 /// `*=` 103 Substring, 104} 105 106/// A combinator between compound selectors. 107#[derive(Debug, Clone, Copy, PartialEq, Eq)] 108pub enum Combinator { 109 /// Descendant (whitespace) 110 Descendant, 111 /// Child (`>`) 112 Child, 113 /// Adjacent sibling (`+`) 114 AdjacentSibling, 115 /// General sibling (`~`) 116 GeneralSibling, 117} 118 119/// A CSS property declaration. 120#[derive(Debug, Clone, PartialEq)] 121pub struct Declaration { 122 pub property: String, 123 pub value: Vec<ComponentValue>, 124 pub important: bool, 125} 126 127/// A component value in a declaration value. We store these as typed tokens 128/// so downstream code can interpret them without re-tokenizing. 129#[derive(Debug, Clone, PartialEq)] 130pub enum ComponentValue { 131 Ident(String), 132 String(String), 133 Number(f64, NumericType), 134 Percentage(f64), 135 Dimension(f64, NumericType, String), 136 Hash(String, HashType), 137 Function(String, Vec<ComponentValue>), 138 Delim(char), 139 Comma, 140 Whitespace, 141} 142 143// --------------------------------------------------------------------------- 144// Parser 145// --------------------------------------------------------------------------- 146 147/// CSS parser. Consumes tokens and produces AST. 148pub struct Parser { 149 tokens: Vec<Token>, 150 pos: usize, 151} 152 153impl Parser { 154 /// Parse a full stylesheet from CSS source text. 155 pub fn parse(input: &str) -> Stylesheet { 156 let tokens = Tokenizer::tokenize(input); 157 let mut parser = Self { tokens, pos: 0 }; 158 parser.parse_stylesheet() 159 } 160 161 /// Parse a list of declarations (for `style` attribute values). 162 pub fn parse_declarations(input: &str) -> Vec<Declaration> { 163 let tokens = Tokenizer::tokenize(input); 164 let mut parser = Self { tokens, pos: 0 }; 165 parser.parse_declaration_list() 166 } 167 168 // -- Token access ------------------------------------------------------- 169 170 fn peek(&self) -> &Token { 171 self.tokens.get(self.pos).unwrap_or(&Token::Eof) 172 } 173 174 fn advance(&mut self) -> Token { 175 let tok = self.tokens.get(self.pos).cloned().unwrap_or(Token::Eof); 176 if self.pos < self.tokens.len() { 177 self.pos += 1; 178 } 179 tok 180 } 181 182 fn is_eof(&self) -> bool { 183 self.pos >= self.tokens.len() 184 } 185 186 fn skip_whitespace(&mut self) { 187 while matches!(self.peek(), Token::Whitespace) { 188 self.advance(); 189 } 190 } 191 192 // -- Stylesheet parsing ------------------------------------------------- 193 194 fn parse_stylesheet(&mut self) -> Stylesheet { 195 let mut rules = Vec::new(); 196 loop { 197 self.skip_whitespace(); 198 if self.is_eof() { 199 break; 200 } 201 match self.peek() { 202 Token::AtKeyword(_) => { 203 if let Some(rule) = self.parse_at_rule() { 204 rules.push(rule); 205 } 206 } 207 Token::Cdo | Token::Cdc => { 208 self.advance(); 209 } 210 _ => { 211 if let Some(rule) = self.parse_style_rule() { 212 rules.push(Rule::Style(rule)); 213 } 214 } 215 } 216 } 217 Stylesheet { rules } 218 } 219 220 // -- At-rules ----------------------------------------------------------- 221 222 fn parse_at_rule(&mut self) -> Option<Rule> { 223 let name = match self.advance() { 224 Token::AtKeyword(name) => name, 225 _ => return None, 226 }; 227 228 match name.to_ascii_lowercase().as_str() { 229 "media" => self.parse_media_rule(), 230 "import" => self.parse_import_rule(), 231 _ => { 232 // Unknown at-rule: skip to end of block or semicolon 233 self.skip_at_rule_body(); 234 None 235 } 236 } 237 } 238 239 fn parse_media_rule(&mut self) -> Option<Rule> { 240 self.skip_whitespace(); 241 242 // Collect prelude tokens as the media query string 243 let mut query = String::new(); 244 loop { 245 match self.peek() { 246 Token::LeftBrace | Token::Eof => break, 247 Token::Whitespace => { 248 if !query.is_empty() { 249 query.push(' '); 250 } 251 self.advance(); 252 } 253 _ => { 254 let tok = self.advance(); 255 query.push_str(&token_to_string(&tok)); 256 } 257 } 258 } 259 260 // Expect `{` 261 if !matches!(self.peek(), Token::LeftBrace) { 262 return None; 263 } 264 self.advance(); 265 266 // Parse nested rules until `}` 267 let mut rules = Vec::new(); 268 loop { 269 self.skip_whitespace(); 270 if self.is_eof() { 271 break; 272 } 273 if matches!(self.peek(), Token::RightBrace) { 274 self.advance(); 275 break; 276 } 277 match self.peek() { 278 Token::AtKeyword(_) => { 279 if let Some(rule) = self.parse_at_rule() { 280 rules.push(rule); 281 } 282 } 283 _ => { 284 if let Some(rule) = self.parse_style_rule() { 285 rules.push(Rule::Style(rule)); 286 } 287 } 288 } 289 } 290 291 Some(Rule::Media(MediaRule { 292 query: query.trim().to_string(), 293 rules, 294 })) 295 } 296 297 fn parse_import_rule(&mut self) -> Option<Rule> { 298 self.skip_whitespace(); 299 let url = match self.peek() { 300 Token::String(_) => { 301 if let Token::String(s) = self.advance() { 302 s 303 } else { 304 unreachable!() 305 } 306 } 307 Token::Url(_) => { 308 if let Token::Url(s) = self.advance() { 309 s 310 } else { 311 unreachable!() 312 } 313 } 314 Token::Function(name) if name.eq_ignore_ascii_case("url") => { 315 self.advance(); 316 self.skip_whitespace(); 317 let url = match self.advance() { 318 Token::String(s) => s, 319 _ => { 320 self.skip_to_semicolon(); 321 return None; 322 } 323 }; 324 self.skip_whitespace(); 325 // consume closing paren 326 if matches!(self.peek(), Token::RightParen) { 327 self.advance(); 328 } 329 url 330 } 331 _ => { 332 self.skip_to_semicolon(); 333 return None; 334 } 335 }; 336 // Consume optional trailing tokens until semicolon 337 self.skip_to_semicolon(); 338 Some(Rule::Import(ImportRule { url })) 339 } 340 341 fn skip_at_rule_body(&mut self) { 342 let mut brace_depth = 0; 343 loop { 344 match self.peek() { 345 Token::Eof => return, 346 Token::Semicolon if brace_depth == 0 => { 347 self.advance(); 348 return; 349 } 350 Token::LeftBrace => { 351 brace_depth += 1; 352 self.advance(); 353 } 354 Token::RightBrace => { 355 if brace_depth == 0 { 356 self.advance(); 357 return; 358 } 359 brace_depth -= 1; 360 self.advance(); 361 if brace_depth == 0 { 362 return; 363 } 364 } 365 _ => { 366 self.advance(); 367 } 368 } 369 } 370 } 371 372 fn skip_to_semicolon(&mut self) { 373 loop { 374 match self.peek() { 375 Token::Eof | Token::Semicolon => { 376 if matches!(self.peek(), Token::Semicolon) { 377 self.advance(); 378 } 379 return; 380 } 381 _ => { 382 self.advance(); 383 } 384 } 385 } 386 } 387 388 // -- Style rules -------------------------------------------------------- 389 390 fn parse_style_rule(&mut self) -> Option<StyleRule> { 391 let selectors = self.parse_selector_list(); 392 393 // Expect `{` 394 self.skip_whitespace(); 395 if !matches!(self.peek(), Token::LeftBrace) { 396 // Error recovery: skip to end of block or next rule 397 self.skip_at_rule_body(); 398 return None; 399 } 400 self.advance(); 401 402 let declarations = self.parse_declaration_list_until_brace(); 403 404 // Consume `}` 405 if matches!(self.peek(), Token::RightBrace) { 406 self.advance(); 407 } 408 409 if selectors.selectors.is_empty() { 410 return None; 411 } 412 413 Some(StyleRule { 414 selectors, 415 declarations, 416 }) 417 } 418 419 // -- Selector parsing --------------------------------------------------- 420 421 fn parse_selector_list(&mut self) -> SelectorList { 422 let mut selectors = Vec::new(); 423 self.skip_whitespace(); 424 425 if let Some(sel) = self.parse_selector() { 426 selectors.push(sel); 427 } 428 429 loop { 430 self.skip_whitespace(); 431 if !matches!(self.peek(), Token::Comma) { 432 break; 433 } 434 self.advance(); // consume comma 435 self.skip_whitespace(); 436 if let Some(sel) = self.parse_selector() { 437 selectors.push(sel); 438 } 439 } 440 441 SelectorList { selectors } 442 } 443 444 fn parse_selector(&mut self) -> Option<Selector> { 445 let mut components = Vec::new(); 446 let mut last_was_compound = false; 447 let mut had_whitespace = false; 448 449 loop { 450 // Check for end of selector 451 match self.peek() { 452 Token::Comma 453 | Token::LeftBrace 454 | Token::RightBrace 455 | Token::Semicolon 456 | Token::Eof => break, 457 Token::Whitespace => { 458 had_whitespace = true; 459 self.advance(); 460 continue; 461 } 462 _ => {} 463 } 464 465 // Check for explicit combinator 466 let combinator = match self.peek() { 467 Token::Delim('>') => { 468 self.advance(); 469 Some(Combinator::Child) 470 } 471 Token::Delim('+') => { 472 self.advance(); 473 Some(Combinator::AdjacentSibling) 474 } 475 Token::Delim('~') => { 476 self.advance(); 477 Some(Combinator::GeneralSibling) 478 } 479 _ => None, 480 }; 481 482 if let Some(comb) = combinator { 483 if last_was_compound { 484 components.push(SelectorComponent::Combinator(comb)); 485 last_was_compound = false; 486 had_whitespace = false; 487 } 488 self.skip_whitespace(); 489 continue; 490 } 491 492 // Implicit descendant combinator if there was whitespace 493 if had_whitespace && last_was_compound { 494 components.push(SelectorComponent::Combinator(Combinator::Descendant)); 495 had_whitespace = false; 496 } 497 498 // Parse compound selector 499 if let Some(compound) = self.parse_compound_selector() { 500 components.push(SelectorComponent::Compound(compound)); 501 last_was_compound = true; 502 } else { 503 break; 504 } 505 } 506 507 if components.is_empty() { 508 None 509 } else { 510 Some(Selector { components }) 511 } 512 } 513 514 fn parse_compound_selector(&mut self) -> Option<CompoundSelector> { 515 let mut simple = Vec::new(); 516 517 loop { 518 match self.peek() { 519 // Type or universal 520 Token::Ident(_) if simple.is_empty() || !has_type_or_universal(&simple) => { 521 if let Token::Ident(name) = self.advance() { 522 simple.push(SimpleSelector::Type(name.to_ascii_lowercase())); 523 } 524 } 525 Token::Delim('*') if simple.is_empty() || !has_type_or_universal(&simple) => { 526 self.advance(); 527 simple.push(SimpleSelector::Universal); 528 } 529 // Class 530 Token::Delim('.') => { 531 self.advance(); 532 match self.advance() { 533 Token::Ident(name) => simple.push(SimpleSelector::Class(name)), 534 _ => break, 535 } 536 } 537 // ID 538 Token::Hash(_, HashType::Id) => { 539 if let Token::Hash(name, _) = self.advance() { 540 simple.push(SimpleSelector::Id(name)); 541 } 542 } 543 // Attribute 544 Token::LeftBracket => { 545 self.advance(); 546 if let Some(attr) = self.parse_attribute_selector() { 547 simple.push(SimpleSelector::Attribute(attr)); 548 } 549 } 550 // Pseudo-class 551 Token::Colon => { 552 self.advance(); 553 match self.advance() { 554 Token::Ident(name) => { 555 simple.push(SimpleSelector::PseudoClass(name.to_ascii_lowercase())) 556 } 557 Token::Function(name) => { 558 // Parse pseudo-class with arguments: skip to closing paren 559 let mut pname = name.to_ascii_lowercase(); 560 pname.push('('); 561 let mut depth = 1; 562 loop { 563 match self.peek() { 564 Token::Eof => break, 565 Token::LeftParen => { 566 depth += 1; 567 pname.push('('); 568 self.advance(); 569 } 570 Token::RightParen => { 571 depth -= 1; 572 if depth == 0 { 573 self.advance(); 574 break; 575 } 576 pname.push(')'); 577 self.advance(); 578 } 579 _ => { 580 let tok = self.advance(); 581 pname.push_str(&token_to_string(&tok)); 582 } 583 } 584 } 585 pname.push(')'); 586 simple.push(SimpleSelector::PseudoClass(pname)); 587 } 588 _ => break, 589 } 590 } 591 // Ident after already having a type selector → new compound starts 592 Token::Ident(_) => break, 593 _ => break, 594 } 595 } 596 597 if simple.is_empty() { 598 None 599 } else { 600 Some(CompoundSelector { simple }) 601 } 602 } 603 604 fn parse_attribute_selector(&mut self) -> Option<AttributeSelector> { 605 self.skip_whitespace(); 606 607 let name = match self.advance() { 608 Token::Ident(name) => name.to_ascii_lowercase(), 609 _ => { 610 self.skip_to_bracket_close(); 611 return None; 612 } 613 }; 614 615 self.skip_whitespace(); 616 617 // Check for close bracket (presence-only selector) 618 if matches!(self.peek(), Token::RightBracket) { 619 self.advance(); 620 return Some(AttributeSelector { 621 name, 622 op: None, 623 value: None, 624 }); 625 } 626 627 // Parse operator 628 let op = match self.peek() { 629 Token::Delim('=') => { 630 self.advance(); 631 AttributeOp::Exact 632 } 633 Token::Delim('~') => { 634 self.advance(); 635 if matches!(self.peek(), Token::Delim('=')) { 636 self.advance(); 637 } 638 AttributeOp::Includes 639 } 640 Token::Delim('|') => { 641 self.advance(); 642 if matches!(self.peek(), Token::Delim('=')) { 643 self.advance(); 644 } 645 AttributeOp::DashMatch 646 } 647 Token::Delim('^') => { 648 self.advance(); 649 if matches!(self.peek(), Token::Delim('=')) { 650 self.advance(); 651 } 652 AttributeOp::Prefix 653 } 654 Token::Delim('$') => { 655 self.advance(); 656 if matches!(self.peek(), Token::Delim('=')) { 657 self.advance(); 658 } 659 AttributeOp::Suffix 660 } 661 Token::Delim('*') => { 662 self.advance(); 663 if matches!(self.peek(), Token::Delim('=')) { 664 self.advance(); 665 } 666 AttributeOp::Substring 667 } 668 _ => { 669 self.skip_to_bracket_close(); 670 return None; 671 } 672 }; 673 674 self.skip_whitespace(); 675 676 // Parse value 677 let value = match self.advance() { 678 Token::Ident(v) => v, 679 Token::String(v) => v, 680 _ => { 681 self.skip_to_bracket_close(); 682 return None; 683 } 684 }; 685 686 self.skip_whitespace(); 687 688 // Consume closing bracket 689 if matches!(self.peek(), Token::RightBracket) { 690 self.advance(); 691 } 692 693 Some(AttributeSelector { 694 name, 695 op: Some(op), 696 value: Some(value), 697 }) 698 } 699 700 fn skip_to_bracket_close(&mut self) { 701 loop { 702 match self.peek() { 703 Token::RightBracket | Token::Eof => { 704 if matches!(self.peek(), Token::RightBracket) { 705 self.advance(); 706 } 707 return; 708 } 709 _ => { 710 self.advance(); 711 } 712 } 713 } 714 } 715 716 // -- Declaration parsing ------------------------------------------------ 717 718 fn parse_declaration_list(&mut self) -> Vec<Declaration> { 719 let mut declarations = Vec::new(); 720 loop { 721 self.skip_whitespace(); 722 if self.is_eof() { 723 break; 724 } 725 if matches!(self.peek(), Token::Semicolon) { 726 self.advance(); 727 continue; 728 } 729 if let Some(decl) = self.parse_declaration() { 730 declarations.push(decl); 731 } 732 } 733 declarations 734 } 735 736 fn parse_declaration_list_until_brace(&mut self) -> Vec<Declaration> { 737 let mut declarations = Vec::new(); 738 loop { 739 self.skip_whitespace(); 740 if self.is_eof() || matches!(self.peek(), Token::RightBrace) { 741 break; 742 } 743 if matches!(self.peek(), Token::Semicolon) { 744 self.advance(); 745 continue; 746 } 747 if let Some(decl) = self.parse_declaration() { 748 declarations.push(decl); 749 } 750 } 751 declarations 752 } 753 754 fn parse_declaration(&mut self) -> Option<Declaration> { 755 // Property name 756 let property = match self.peek() { 757 Token::Ident(_) => { 758 if let Token::Ident(name) = self.advance() { 759 name.to_ascii_lowercase() 760 } else { 761 unreachable!() 762 } 763 } 764 _ => { 765 // Error recovery: skip to next semicolon or closing brace 766 self.skip_declaration_error(); 767 return None; 768 } 769 }; 770 771 self.skip_whitespace(); 772 773 // Expect colon 774 if !matches!(self.peek(), Token::Colon) { 775 self.skip_declaration_error(); 776 return None; 777 } 778 self.advance(); 779 780 self.skip_whitespace(); 781 782 // Parse value 783 let (value, important) = self.parse_declaration_value(); 784 785 if value.is_empty() { 786 return None; 787 } 788 789 Some(Declaration { 790 property, 791 value, 792 important, 793 }) 794 } 795 796 fn parse_declaration_value(&mut self) -> (Vec<ComponentValue>, bool) { 797 let mut values = Vec::new(); 798 let mut important = false; 799 800 loop { 801 match self.peek() { 802 Token::Semicolon | Token::RightBrace | Token::Eof => break, 803 Token::Whitespace => { 804 self.advance(); 805 // Only add whitespace if there are already values and we're 806 // not at the end of the declaration 807 if !values.is_empty() 808 && !matches!( 809 self.peek(), 810 Token::Semicolon | Token::RightBrace | Token::Eof 811 ) 812 { 813 values.push(ComponentValue::Whitespace); 814 } 815 } 816 Token::Delim('!') => { 817 self.advance(); 818 self.skip_whitespace(); 819 if let Token::Ident(ref s) = self.peek() { 820 if s.eq_ignore_ascii_case("important") { 821 important = true; 822 self.advance(); 823 } 824 } 825 } 826 _ => { 827 if let Some(cv) = self.parse_component_value() { 828 values.push(cv); 829 } 830 } 831 } 832 } 833 834 // Trim trailing whitespace 835 while matches!(values.last(), Some(ComponentValue::Whitespace)) { 836 values.pop(); 837 } 838 839 (values, important) 840 } 841 842 fn parse_component_value(&mut self) -> Option<ComponentValue> { 843 match self.advance() { 844 Token::Ident(s) => Some(ComponentValue::Ident(s)), 845 Token::String(s) => Some(ComponentValue::String(s)), 846 Token::Number(n, t) => Some(ComponentValue::Number(n, t)), 847 Token::Percentage(n) => Some(ComponentValue::Percentage(n)), 848 Token::Dimension(n, t, u) => Some(ComponentValue::Dimension(n, t, u)), 849 Token::Hash(s, ht) => Some(ComponentValue::Hash(s, ht)), 850 Token::Comma => Some(ComponentValue::Comma), 851 Token::Delim(c) => Some(ComponentValue::Delim(c)), 852 Token::Function(name) => { 853 let args = self.parse_function_args(); 854 Some(ComponentValue::Function(name, args)) 855 } 856 _ => None, 857 } 858 } 859 860 fn parse_function_args(&mut self) -> Vec<ComponentValue> { 861 let mut args = Vec::new(); 862 loop { 863 match self.peek() { 864 Token::RightParen | Token::Eof => { 865 if matches!(self.peek(), Token::RightParen) { 866 self.advance(); 867 } 868 break; 869 } 870 Token::Whitespace => { 871 self.advance(); 872 if !args.is_empty() && !matches!(self.peek(), Token::RightParen | Token::Eof) { 873 args.push(ComponentValue::Whitespace); 874 } 875 } 876 _ => { 877 if let Some(cv) = self.parse_component_value() { 878 args.push(cv); 879 } 880 } 881 } 882 } 883 args 884 } 885 886 fn skip_declaration_error(&mut self) { 887 loop { 888 match self.peek() { 889 Token::Semicolon => { 890 self.advance(); 891 return; 892 } 893 Token::RightBrace | Token::Eof => return, 894 _ => { 895 self.advance(); 896 } 897 } 898 } 899 } 900} 901 902// --------------------------------------------------------------------------- 903// Helpers 904// --------------------------------------------------------------------------- 905 906fn has_type_or_universal(selectors: &[SimpleSelector]) -> bool { 907 selectors 908 .iter() 909 .any(|s| matches!(s, SimpleSelector::Type(_) | SimpleSelector::Universal)) 910} 911 912fn token_to_string(token: &Token) -> String { 913 match token { 914 Token::Ident(s) => s.clone(), 915 Token::Function(s) => format!("{s}("), 916 Token::AtKeyword(s) => format!("@{s}"), 917 Token::Hash(s, _) => format!("#{s}"), 918 Token::String(s) => format!("\"{s}\""), 919 Token::Url(s) => format!("url({s})"), 920 Token::Number(n, _) => format!("{n}"), 921 Token::Percentage(n) => format!("{n}%"), 922 Token::Dimension(n, _, u) => format!("{n}{u}"), 923 Token::Whitespace => " ".to_string(), 924 Token::Colon => ":".to_string(), 925 Token::Semicolon => ";".to_string(), 926 Token::Comma => ",".to_string(), 927 Token::LeftBracket => "[".to_string(), 928 Token::RightBracket => "]".to_string(), 929 Token::LeftParen => "(".to_string(), 930 Token::RightParen => ")".to_string(), 931 Token::LeftBrace => "{".to_string(), 932 Token::RightBrace => "}".to_string(), 933 Token::Delim(c) => c.to_string(), 934 Token::Cdo => "<!--".to_string(), 935 Token::Cdc => "-->".to_string(), 936 Token::BadString | Token::BadUrl | Token::Eof => String::new(), 937 } 938} 939 940// --------------------------------------------------------------------------- 941// Tests 942// --------------------------------------------------------------------------- 943 944#[cfg(test)] 945mod tests { 946 use super::*; 947 948 // -- Selector tests ----------------------------------------------------- 949 950 #[test] 951 fn test_type_selector() { 952 let ss = Parser::parse("div { }"); 953 assert_eq!(ss.rules.len(), 1); 954 let rule = match &ss.rules[0] { 955 Rule::Style(r) => r, 956 _ => panic!("expected style rule"), 957 }; 958 assert_eq!(rule.selectors.selectors.len(), 1); 959 let sel = &rule.selectors.selectors[0]; 960 assert_eq!(sel.components.len(), 1); 961 match &sel.components[0] { 962 SelectorComponent::Compound(c) => { 963 assert_eq!(c.simple.len(), 1); 964 assert_eq!(c.simple[0], SimpleSelector::Type("div".into())); 965 } 966 _ => panic!("expected compound"), 967 } 968 } 969 970 #[test] 971 fn test_universal_selector() { 972 let ss = Parser::parse("* { }"); 973 let rule = match &ss.rules[0] { 974 Rule::Style(r) => r, 975 _ => panic!("expected style rule"), 976 }; 977 let sel = &rule.selectors.selectors[0]; 978 match &sel.components[0] { 979 SelectorComponent::Compound(c) => { 980 assert_eq!(c.simple[0], SimpleSelector::Universal); 981 } 982 _ => panic!("expected compound"), 983 } 984 } 985 986 #[test] 987 fn test_class_selector() { 988 let ss = Parser::parse(".foo { }"); 989 let rule = match &ss.rules[0] { 990 Rule::Style(r) => r, 991 _ => panic!("expected style rule"), 992 }; 993 let sel = &rule.selectors.selectors[0]; 994 match &sel.components[0] { 995 SelectorComponent::Compound(c) => { 996 assert_eq!(c.simple[0], SimpleSelector::Class("foo".into())); 997 } 998 _ => panic!("expected compound"), 999 } 1000 } 1001 1002 #[test] 1003 fn test_id_selector() { 1004 let ss = Parser::parse("#main { }"); 1005 let rule = match &ss.rules[0] { 1006 Rule::Style(r) => r, 1007 _ => panic!("expected style rule"), 1008 }; 1009 let sel = &rule.selectors.selectors[0]; 1010 match &sel.components[0] { 1011 SelectorComponent::Compound(c) => { 1012 assert_eq!(c.simple[0], SimpleSelector::Id("main".into())); 1013 } 1014 _ => panic!("expected compound"), 1015 } 1016 } 1017 1018 #[test] 1019 fn test_compound_selector() { 1020 let ss = Parser::parse("div.foo#bar { }"); 1021 let rule = match &ss.rules[0] { 1022 Rule::Style(r) => r, 1023 _ => panic!("expected style rule"), 1024 }; 1025 let sel = &rule.selectors.selectors[0]; 1026 match &sel.components[0] { 1027 SelectorComponent::Compound(c) => { 1028 assert_eq!(c.simple.len(), 3); 1029 assert_eq!(c.simple[0], SimpleSelector::Type("div".into())); 1030 assert_eq!(c.simple[1], SimpleSelector::Class("foo".into())); 1031 assert_eq!(c.simple[2], SimpleSelector::Id("bar".into())); 1032 } 1033 _ => panic!("expected compound"), 1034 } 1035 } 1036 1037 #[test] 1038 fn test_selector_list() { 1039 let ss = Parser::parse("h1, h2, h3 { }"); 1040 let rule = match &ss.rules[0] { 1041 Rule::Style(r) => r, 1042 _ => panic!("expected style rule"), 1043 }; 1044 assert_eq!(rule.selectors.selectors.len(), 3); 1045 } 1046 1047 #[test] 1048 fn test_descendant_combinator() { 1049 let ss = Parser::parse("div p { }"); 1050 let rule = match &ss.rules[0] { 1051 Rule::Style(r) => r, 1052 _ => panic!("expected style rule"), 1053 }; 1054 let sel = &rule.selectors.selectors[0]; 1055 assert_eq!(sel.components.len(), 3); 1056 assert!(matches!( 1057 sel.components[1], 1058 SelectorComponent::Combinator(Combinator::Descendant) 1059 )); 1060 } 1061 1062 #[test] 1063 fn test_child_combinator() { 1064 let ss = Parser::parse("div > p { }"); 1065 let rule = match &ss.rules[0] { 1066 Rule::Style(r) => r, 1067 _ => panic!("expected style rule"), 1068 }; 1069 let sel = &rule.selectors.selectors[0]; 1070 assert_eq!(sel.components.len(), 3); 1071 assert!(matches!( 1072 sel.components[1], 1073 SelectorComponent::Combinator(Combinator::Child) 1074 )); 1075 } 1076 1077 #[test] 1078 fn test_adjacent_sibling_combinator() { 1079 let ss = Parser::parse("h1 + p { }"); 1080 let rule = match &ss.rules[0] { 1081 Rule::Style(r) => r, 1082 _ => panic!("expected style rule"), 1083 }; 1084 let sel = &rule.selectors.selectors[0]; 1085 assert!(matches!( 1086 sel.components[1], 1087 SelectorComponent::Combinator(Combinator::AdjacentSibling) 1088 )); 1089 } 1090 1091 #[test] 1092 fn test_general_sibling_combinator() { 1093 let ss = Parser::parse("h1 ~ p { }"); 1094 let rule = match &ss.rules[0] { 1095 Rule::Style(r) => r, 1096 _ => panic!("expected style rule"), 1097 }; 1098 let sel = &rule.selectors.selectors[0]; 1099 assert!(matches!( 1100 sel.components[1], 1101 SelectorComponent::Combinator(Combinator::GeneralSibling) 1102 )); 1103 } 1104 1105 #[test] 1106 fn test_attribute_presence() { 1107 let ss = Parser::parse("[disabled] { }"); 1108 let rule = match &ss.rules[0] { 1109 Rule::Style(r) => r, 1110 _ => panic!("expected style rule"), 1111 }; 1112 let sel = &rule.selectors.selectors[0]; 1113 match &sel.components[0] { 1114 SelectorComponent::Compound(c) => match &c.simple[0] { 1115 SimpleSelector::Attribute(attr) => { 1116 assert_eq!(attr.name, "disabled"); 1117 assert!(attr.op.is_none()); 1118 assert!(attr.value.is_none()); 1119 } 1120 _ => panic!("expected attribute selector"), 1121 }, 1122 _ => panic!("expected compound"), 1123 } 1124 } 1125 1126 #[test] 1127 fn test_attribute_exact() { 1128 let ss = Parser::parse("[type=\"text\"] { }"); 1129 let rule = match &ss.rules[0] { 1130 Rule::Style(r) => r, 1131 _ => panic!("expected style rule"), 1132 }; 1133 let sel = &rule.selectors.selectors[0]; 1134 match &sel.components[0] { 1135 SelectorComponent::Compound(c) => match &c.simple[0] { 1136 SimpleSelector::Attribute(attr) => { 1137 assert_eq!(attr.name, "type"); 1138 assert_eq!(attr.op, Some(AttributeOp::Exact)); 1139 assert_eq!(attr.value, Some("text".into())); 1140 } 1141 _ => panic!("expected attribute selector"), 1142 }, 1143 _ => panic!("expected compound"), 1144 } 1145 } 1146 1147 #[test] 1148 fn test_attribute_operators() { 1149 let ops = vec![ 1150 ("[a~=b] { }", AttributeOp::Includes), 1151 ("[a|=b] { }", AttributeOp::DashMatch), 1152 ("[a^=b] { }", AttributeOp::Prefix), 1153 ("[a$=b] { }", AttributeOp::Suffix), 1154 ("[a*=b] { }", AttributeOp::Substring), 1155 ]; 1156 for (input, expected_op) in ops { 1157 let ss = Parser::parse(input); 1158 let rule = match &ss.rules[0] { 1159 Rule::Style(r) => r, 1160 _ => panic!("expected style rule"), 1161 }; 1162 let sel = &rule.selectors.selectors[0]; 1163 match &sel.components[0] { 1164 SelectorComponent::Compound(c) => match &c.simple[0] { 1165 SimpleSelector::Attribute(attr) => { 1166 assert_eq!(attr.op, Some(expected_op), "failed for {input}"); 1167 } 1168 _ => panic!("expected attribute selector"), 1169 }, 1170 _ => panic!("expected compound"), 1171 } 1172 } 1173 } 1174 1175 #[test] 1176 fn test_pseudo_class() { 1177 let ss = Parser::parse("a:hover { }"); 1178 let rule = match &ss.rules[0] { 1179 Rule::Style(r) => r, 1180 _ => panic!("expected style rule"), 1181 }; 1182 let sel = &rule.selectors.selectors[0]; 1183 match &sel.components[0] { 1184 SelectorComponent::Compound(c) => { 1185 assert_eq!(c.simple.len(), 2); 1186 assert_eq!(c.simple[0], SimpleSelector::Type("a".into())); 1187 assert_eq!(c.simple[1], SimpleSelector::PseudoClass("hover".into())); 1188 } 1189 _ => panic!("expected compound"), 1190 } 1191 } 1192 1193 #[test] 1194 fn test_pseudo_class_first_child() { 1195 let ss = Parser::parse("p:first-child { }"); 1196 let rule = match &ss.rules[0] { 1197 Rule::Style(r) => r, 1198 _ => panic!("expected style rule"), 1199 }; 1200 let sel = &rule.selectors.selectors[0]; 1201 match &sel.components[0] { 1202 SelectorComponent::Compound(c) => { 1203 assert_eq!( 1204 c.simple[1], 1205 SimpleSelector::PseudoClass("first-child".into()) 1206 ); 1207 } 1208 _ => panic!("expected compound"), 1209 } 1210 } 1211 1212 // -- Declaration tests -------------------------------------------------- 1213 1214 #[test] 1215 fn test_simple_declaration() { 1216 let ss = Parser::parse("p { color: red; }"); 1217 let rule = match &ss.rules[0] { 1218 Rule::Style(r) => r, 1219 _ => panic!("expected style rule"), 1220 }; 1221 assert_eq!(rule.declarations.len(), 1); 1222 assert_eq!(rule.declarations[0].property, "color"); 1223 assert_eq!(rule.declarations[0].value.len(), 1); 1224 assert_eq!( 1225 rule.declarations[0].value[0], 1226 ComponentValue::Ident("red".into()) 1227 ); 1228 assert!(!rule.declarations[0].important); 1229 } 1230 1231 #[test] 1232 fn test_multiple_declarations() { 1233 let ss = Parser::parse("p { color: red; font-size: 16px; }"); 1234 let rule = match &ss.rules[0] { 1235 Rule::Style(r) => r, 1236 _ => panic!("expected style rule"), 1237 }; 1238 assert_eq!(rule.declarations.len(), 2); 1239 assert_eq!(rule.declarations[0].property, "color"); 1240 assert_eq!(rule.declarations[1].property, "font-size"); 1241 } 1242 1243 #[test] 1244 fn test_important_declaration() { 1245 let ss = Parser::parse("p { color: red !important; }"); 1246 let rule = match &ss.rules[0] { 1247 Rule::Style(r) => r, 1248 _ => panic!("expected style rule"), 1249 }; 1250 assert!(rule.declarations[0].important); 1251 } 1252 1253 #[test] 1254 fn test_declaration_with_function() { 1255 let ss = Parser::parse("p { color: rgb(255, 0, 0); }"); 1256 let rule = match &ss.rules[0] { 1257 Rule::Style(r) => r, 1258 _ => panic!("expected style rule"), 1259 }; 1260 assert_eq!(rule.declarations[0].property, "color"); 1261 match &rule.declarations[0].value[0] { 1262 ComponentValue::Function(name, args) => { 1263 assert_eq!(name, "rgb"); 1264 // 255, 0, 0 → Number, Comma, WS, Number, Comma, WS, Number 1265 assert!(!args.is_empty()); 1266 } 1267 _ => panic!("expected function value"), 1268 } 1269 } 1270 1271 #[test] 1272 fn test_declaration_with_hash_color() { 1273 let ss = Parser::parse("p { color: #ff0000; }"); 1274 let rule = match &ss.rules[0] { 1275 Rule::Style(r) => r, 1276 _ => panic!("expected style rule"), 1277 }; 1278 assert_eq!( 1279 rule.declarations[0].value[0], 1280 ComponentValue::Hash("ff0000".into(), HashType::Id) 1281 ); 1282 } 1283 1284 #[test] 1285 fn test_declaration_with_dimension() { 1286 let ss = Parser::parse("p { margin: 10px; }"); 1287 let rule = match &ss.rules[0] { 1288 Rule::Style(r) => r, 1289 _ => panic!("expected style rule"), 1290 }; 1291 assert_eq!( 1292 rule.declarations[0].value[0], 1293 ComponentValue::Dimension(10.0, NumericType::Integer, "px".into()) 1294 ); 1295 } 1296 1297 #[test] 1298 fn test_declaration_with_percentage() { 1299 let ss = Parser::parse("p { width: 50%; }"); 1300 let rule = match &ss.rules[0] { 1301 Rule::Style(r) => r, 1302 _ => panic!("expected style rule"), 1303 }; 1304 assert_eq!( 1305 rule.declarations[0].value[0], 1306 ComponentValue::Percentage(50.0) 1307 ); 1308 } 1309 1310 #[test] 1311 fn test_parse_inline_style() { 1312 let decls = Parser::parse_declarations("color: red; font-size: 16px"); 1313 assert_eq!(decls.len(), 2); 1314 assert_eq!(decls[0].property, "color"); 1315 assert_eq!(decls[1].property, "font-size"); 1316 } 1317 1318 // -- Error recovery tests ----------------------------------------------- 1319 1320 #[test] 1321 fn test_invalid_declaration_skipped() { 1322 let ss = Parser::parse("p { ??? ; color: red; }"); 1323 let rule = match &ss.rules[0] { 1324 Rule::Style(r) => r, 1325 _ => panic!("expected style rule"), 1326 }; 1327 // The invalid declaration should be skipped, color should remain 1328 assert_eq!(rule.declarations.len(), 1); 1329 assert_eq!(rule.declarations[0].property, "color"); 1330 } 1331 1332 #[test] 1333 fn test_missing_colon_skipped() { 1334 let ss = Parser::parse("p { color red; font-size: 16px; }"); 1335 let rule = match &ss.rules[0] { 1336 Rule::Style(r) => r, 1337 _ => panic!("expected style rule"), 1338 }; 1339 assert_eq!(rule.declarations.len(), 1); 1340 assert_eq!(rule.declarations[0].property, "font-size"); 1341 } 1342 1343 #[test] 1344 fn test_empty_stylesheet() { 1345 let ss = Parser::parse(""); 1346 assert_eq!(ss.rules.len(), 0); 1347 } 1348 1349 #[test] 1350 fn test_empty_rule() { 1351 let ss = Parser::parse("p { }"); 1352 let rule = match &ss.rules[0] { 1353 Rule::Style(r) => r, 1354 _ => panic!("expected style rule"), 1355 }; 1356 assert_eq!(rule.declarations.len(), 0); 1357 } 1358 1359 #[test] 1360 fn test_multiple_rules() { 1361 let ss = Parser::parse("h1 { color: blue; } p { color: red; }"); 1362 assert_eq!(ss.rules.len(), 2); 1363 } 1364 1365 // -- @-rule tests ------------------------------------------------------- 1366 1367 #[test] 1368 fn test_import_rule_string() { 1369 let ss = Parser::parse("@import \"style.css\";"); 1370 assert_eq!(ss.rules.len(), 1); 1371 match &ss.rules[0] { 1372 Rule::Import(r) => assert_eq!(r.url, "style.css"), 1373 _ => panic!("expected import rule"), 1374 } 1375 } 1376 1377 #[test] 1378 fn test_import_rule_url() { 1379 let ss = Parser::parse("@import url(style.css);"); 1380 assert_eq!(ss.rules.len(), 1); 1381 match &ss.rules[0] { 1382 Rule::Import(r) => assert_eq!(r.url, "style.css"), 1383 _ => panic!("expected import rule"), 1384 } 1385 } 1386 1387 #[test] 1388 fn test_media_rule() { 1389 let ss = Parser::parse("@media screen { p { color: red; } }"); 1390 assert_eq!(ss.rules.len(), 1); 1391 match &ss.rules[0] { 1392 Rule::Media(m) => { 1393 assert_eq!(m.query, "screen"); 1394 assert_eq!(m.rules.len(), 1); 1395 } 1396 _ => panic!("expected media rule"), 1397 } 1398 } 1399 1400 #[test] 1401 fn test_media_rule_complex_query() { 1402 let ss = Parser::parse("@media screen and (max-width: 600px) { p { font-size: 14px; } }"); 1403 match &ss.rules[0] { 1404 Rule::Media(m) => { 1405 assert!(m.query.contains("screen")); 1406 assert!(m.query.contains("max-width")); 1407 assert_eq!(m.rules.len(), 1); 1408 } 1409 _ => panic!("expected media rule"), 1410 } 1411 } 1412 1413 #[test] 1414 fn test_unknown_at_rule_skipped() { 1415 let ss = Parser::parse("@charset \"UTF-8\"; p { color: red; }"); 1416 assert_eq!(ss.rules.len(), 1); 1417 match &ss.rules[0] { 1418 Rule::Style(r) => assert_eq!(r.declarations[0].property, "color"), 1419 _ => panic!("expected style rule"), 1420 } 1421 } 1422 1423 // -- Integration tests -------------------------------------------------- 1424 1425 #[test] 1426 fn test_real_css() { 1427 let css = r#" 1428 body { 1429 margin: 0; 1430 font-family: sans-serif; 1431 background-color: #fff; 1432 } 1433 h1 { 1434 color: #333; 1435 font-size: 24px; 1436 } 1437 .container { 1438 max-width: 960px; 1439 margin: 0 auto; 1440 } 1441 a:hover { 1442 color: blue; 1443 text-decoration: underline; 1444 } 1445 "#; 1446 let ss = Parser::parse(css); 1447 assert_eq!(ss.rules.len(), 4); 1448 } 1449 1450 #[test] 1451 fn test_declaration_no_trailing_semicolon() { 1452 let ss = Parser::parse("p { color: red }"); 1453 let rule = match &ss.rules[0] { 1454 Rule::Style(r) => r, 1455 _ => panic!("expected style rule"), 1456 }; 1457 assert_eq!(rule.declarations.len(), 1); 1458 assert_eq!(rule.declarations[0].property, "color"); 1459 } 1460 1461 #[test] 1462 fn test_multi_value_declaration() { 1463 let ss = Parser::parse("p { margin: 10px 20px 30px 40px; }"); 1464 let rule = match &ss.rules[0] { 1465 Rule::Style(r) => r, 1466 _ => panic!("expected style rule"), 1467 }; 1468 // 10px WS 20px WS 30px WS 40px 1469 assert_eq!(rule.declarations[0].value.len(), 7); 1470 } 1471 1472 #[test] 1473 fn test_cdo_cdc_ignored() { 1474 let ss = Parser::parse("<!-- p { color: red; } -->"); 1475 assert_eq!(ss.rules.len(), 1); 1476 } 1477}