we (web engine): Experimental web browser project to understand the limits of Claude
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 2174 lines 68 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::animations::{Keyframe, KeyframeSelector}; 6use crate::media::{parse_media_query_list, MediaQueryList}; 7use crate::tokenizer::{HashType, NumericType, Token, Tokenizer}; 8 9// --------------------------------------------------------------------------- 10// AST types 11// --------------------------------------------------------------------------- 12 13/// A parsed CSS stylesheet: a list of rules. 14#[derive(Debug, Clone, PartialEq)] 15pub struct Stylesheet { 16 pub rules: Vec<Rule>, 17} 18 19/// A top-level rule: either a style rule or an at-rule. 20#[derive(Debug, Clone, PartialEq)] 21pub enum Rule { 22 Style(StyleRule), 23 Media(MediaRule), 24 Import(ImportRule), 25 Keyframes(KeyframesRule), 26 FontFace(FontFaceRule), 27} 28 29/// A `@keyframes` rule with a name and a list of keyframes. 30#[derive(Debug, Clone, PartialEq)] 31pub struct KeyframesRule { 32 pub name: String, 33 pub keyframes: Vec<Keyframe>, 34} 35 36/// A style rule: selector list + declarations. 37#[derive(Debug, Clone, PartialEq)] 38pub struct StyleRule { 39 pub selectors: SelectorList, 40 pub declarations: Vec<Declaration>, 41} 42 43/// A `@media` rule with parsed media queries and nested rules. 44#[derive(Debug, Clone, PartialEq)] 45pub struct MediaRule { 46 pub queries: MediaQueryList, 47 pub rules: Vec<Rule>, 48} 49 50/// An `@import` rule with a URL. 51#[derive(Debug, Clone, PartialEq)] 52pub struct ImportRule { 53 pub url: String, 54} 55 56/// A `@font-face` rule describing a web font. 57#[derive(Debug, Clone, PartialEq)] 58pub struct FontFaceRule { 59 /// The font family name declared for this font face. 60 pub family: String, 61 /// `src` descriptor: list of font sources (currently only `url()` references). 62 pub sources: Vec<FontFaceSource>, 63 /// `font-weight`: numeric weight (100–900). Defaults to 400. 64 pub weight: u16, 65 /// `font-style`: normal, italic, or oblique. 66 pub style: FontFaceStyle, 67 /// `font-display`: controls rendering behavior during font load. 68 pub display: FontDisplay, 69} 70 71/// A single source in a `@font-face` `src` descriptor. 72#[derive(Debug, Clone, PartialEq)] 73pub struct FontFaceSource { 74 /// URL to the font file. 75 pub url: String, 76 /// Optional `format()` hint (e.g., "truetype", "opentype", "woff2"). 77 pub format: Option<String>, 78} 79 80/// `font-style` descriptor values for `@font-face`. 81#[derive(Debug, Clone, Copy, PartialEq, Eq)] 82pub enum FontFaceStyle { 83 Normal, 84 Italic, 85 Oblique, 86} 87 88/// `font-display` descriptor values for `@font-face`. 89#[derive(Debug, Clone, Copy, PartialEq, Eq)] 90pub enum FontDisplay { 91 Auto, 92 Block, 93 Swap, 94 Fallback, 95 Optional, 96} 97 98/// A comma-separated list of selectors. 99#[derive(Debug, Clone, PartialEq)] 100pub struct SelectorList { 101 pub selectors: Vec<Selector>, 102} 103 104/// A complex selector: a chain of compound selectors joined by combinators. 105#[derive(Debug, Clone, PartialEq)] 106pub struct Selector { 107 pub components: Vec<SelectorComponent>, 108} 109 110/// Either a compound selector or a combinator between compounds. 111#[derive(Debug, Clone, PartialEq)] 112pub enum SelectorComponent { 113 Compound(CompoundSelector), 114 Combinator(Combinator), 115} 116 117/// A compound selector: a sequence of simple selectors with no combinator. 118#[derive(Debug, Clone, PartialEq)] 119pub struct CompoundSelector { 120 pub simple: Vec<SimpleSelector>, 121} 122 123/// A simple selector. 124#[derive(Debug, Clone, PartialEq)] 125pub enum SimpleSelector { 126 Type(String), 127 Universal, 128 Class(String), 129 Id(String), 130 Attribute(AttributeSelector), 131 PseudoClass(String), 132} 133 134/// An attribute selector. 135#[derive(Debug, Clone, PartialEq)] 136pub struct AttributeSelector { 137 pub name: String, 138 pub op: Option<AttributeOp>, 139 pub value: Option<String>, 140} 141 142/// Attribute matching operator. 143#[derive(Debug, Clone, Copy, PartialEq, Eq)] 144pub enum AttributeOp { 145 /// `=` 146 Exact, 147 /// `~=` 148 Includes, 149 /// `|=` 150 DashMatch, 151 /// `^=` 152 Prefix, 153 /// `$=` 154 Suffix, 155 /// `*=` 156 Substring, 157} 158 159/// A combinator between compound selectors. 160#[derive(Debug, Clone, Copy, PartialEq, Eq)] 161pub enum Combinator { 162 /// Descendant (whitespace) 163 Descendant, 164 /// Child (`>`) 165 Child, 166 /// Adjacent sibling (`+`) 167 AdjacentSibling, 168 /// General sibling (`~`) 169 GeneralSibling, 170} 171 172/// A CSS property declaration. 173#[derive(Debug, Clone, PartialEq)] 174pub struct Declaration { 175 pub property: String, 176 pub value: Vec<ComponentValue>, 177 pub important: bool, 178} 179 180/// A component value in a declaration value. We store these as typed tokens 181/// so downstream code can interpret them without re-tokenizing. 182#[derive(Debug, Clone, PartialEq)] 183pub enum ComponentValue { 184 Ident(String), 185 String(String), 186 Number(f64, NumericType), 187 Percentage(f64), 188 Dimension(f64, NumericType, String), 189 Hash(String, HashType), 190 Function(String, Vec<ComponentValue>), 191 Delim(char), 192 Comma, 193 Whitespace, 194} 195 196// --------------------------------------------------------------------------- 197// Parser 198// --------------------------------------------------------------------------- 199 200/// CSS parser. Consumes tokens and produces AST. 201pub struct Parser { 202 tokens: Vec<Token>, 203 pos: usize, 204} 205 206impl Parser { 207 /// Parse a full stylesheet from CSS source text. 208 pub fn parse(input: &str) -> Stylesheet { 209 let tokens = Tokenizer::tokenize(input); 210 let mut parser = Self { tokens, pos: 0 }; 211 parser.parse_stylesheet() 212 } 213 214 /// Parse a list of declarations (for `style` attribute values). 215 pub fn parse_declarations(input: &str) -> Vec<Declaration> { 216 let tokens = Tokenizer::tokenize(input); 217 let mut parser = Self { tokens, pos: 0 }; 218 parser.parse_declaration_list() 219 } 220 221 /// Parse a selector list from a string (for `querySelector`/`querySelectorAll`). 222 pub fn parse_selectors(input: &str) -> SelectorList { 223 let tokens = Tokenizer::tokenize(input); 224 let mut parser = Self { tokens, pos: 0 }; 225 parser.parse_selector_list() 226 } 227 228 // -- Token access ------------------------------------------------------- 229 230 fn peek(&self) -> &Token { 231 self.tokens.get(self.pos).unwrap_or(&Token::Eof) 232 } 233 234 fn advance(&mut self) -> Token { 235 let tok = self.tokens.get(self.pos).cloned().unwrap_or(Token::Eof); 236 if self.pos < self.tokens.len() { 237 self.pos += 1; 238 } 239 tok 240 } 241 242 fn is_eof(&self) -> bool { 243 self.pos >= self.tokens.len() 244 } 245 246 fn skip_whitespace(&mut self) { 247 while matches!(self.peek(), Token::Whitespace) { 248 self.advance(); 249 } 250 } 251 252 // -- Stylesheet parsing ------------------------------------------------- 253 254 fn parse_stylesheet(&mut self) -> Stylesheet { 255 let mut rules = Vec::new(); 256 loop { 257 self.skip_whitespace(); 258 if self.is_eof() { 259 break; 260 } 261 match self.peek() { 262 Token::AtKeyword(_) => { 263 if let Some(rule) = self.parse_at_rule() { 264 rules.push(rule); 265 } 266 } 267 Token::Cdo | Token::Cdc => { 268 self.advance(); 269 } 270 _ => { 271 if let Some(rule) = self.parse_style_rule() { 272 rules.push(Rule::Style(rule)); 273 } 274 } 275 } 276 } 277 Stylesheet { rules } 278 } 279 280 // -- At-rules ----------------------------------------------------------- 281 282 fn parse_at_rule(&mut self) -> Option<Rule> { 283 let name = match self.advance() { 284 Token::AtKeyword(name) => name, 285 _ => return None, 286 }; 287 288 match name.to_ascii_lowercase().as_str() { 289 "media" => self.parse_media_rule(), 290 "import" => self.parse_import_rule(), 291 "keyframes" | "-webkit-keyframes" | "-moz-keyframes" => self.parse_keyframes_rule(), 292 "font-face" => self.parse_font_face_rule(), 293 _ => { 294 // Unknown at-rule: skip to end of block or semicolon 295 self.skip_at_rule_body(); 296 None 297 } 298 } 299 } 300 301 fn parse_media_rule(&mut self) -> Option<Rule> { 302 self.skip_whitespace(); 303 304 // Collect prelude tokens for structured media query parsing. 305 let mut prelude_tokens = Vec::new(); 306 loop { 307 match self.peek() { 308 Token::LeftBrace | Token::Eof => break, 309 _ => { 310 let tok = self.advance(); 311 prelude_tokens.push(tok); 312 } 313 } 314 } 315 316 let queries = parse_media_query_list(&prelude_tokens); 317 318 // Expect `{` 319 if !matches!(self.peek(), Token::LeftBrace) { 320 return None; 321 } 322 self.advance(); 323 324 // Parse nested rules until `}` 325 let mut rules = Vec::new(); 326 loop { 327 self.skip_whitespace(); 328 if self.is_eof() { 329 break; 330 } 331 if matches!(self.peek(), Token::RightBrace) { 332 self.advance(); 333 break; 334 } 335 match self.peek() { 336 Token::AtKeyword(_) => { 337 if let Some(rule) = self.parse_at_rule() { 338 rules.push(rule); 339 } 340 } 341 _ => { 342 if let Some(rule) = self.parse_style_rule() { 343 rules.push(Rule::Style(rule)); 344 } 345 } 346 } 347 } 348 349 Some(Rule::Media(MediaRule { queries, rules })) 350 } 351 352 fn parse_import_rule(&mut self) -> Option<Rule> { 353 self.skip_whitespace(); 354 let url = match self.peek() { 355 Token::String(_) => { 356 if let Token::String(s) = self.advance() { 357 s 358 } else { 359 unreachable!() 360 } 361 } 362 Token::Url(_) => { 363 if let Token::Url(s) = self.advance() { 364 s 365 } else { 366 unreachable!() 367 } 368 } 369 Token::Function(name) if name.eq_ignore_ascii_case("url") => { 370 self.advance(); 371 self.skip_whitespace(); 372 let url = match self.advance() { 373 Token::String(s) => s, 374 _ => { 375 self.skip_to_semicolon(); 376 return None; 377 } 378 }; 379 self.skip_whitespace(); 380 // consume closing paren 381 if matches!(self.peek(), Token::RightParen) { 382 self.advance(); 383 } 384 url 385 } 386 _ => { 387 self.skip_to_semicolon(); 388 return None; 389 } 390 }; 391 // Consume optional trailing tokens until semicolon 392 self.skip_to_semicolon(); 393 Some(Rule::Import(ImportRule { url })) 394 } 395 396 fn parse_keyframes_rule(&mut self) -> Option<Rule> { 397 self.skip_whitespace(); 398 399 // Parse the animation name (identifier or string) 400 let name = match self.peek() { 401 Token::Ident(_) => { 402 if let Token::Ident(s) = self.advance() { 403 s 404 } else { 405 unreachable!() 406 } 407 } 408 Token::String(_) => { 409 if let Token::String(s) = self.advance() { 410 s 411 } else { 412 unreachable!() 413 } 414 } 415 _ => { 416 self.skip_at_rule_body(); 417 return None; 418 } 419 }; 420 421 self.skip_whitespace(); 422 423 // Expect `{` 424 if !matches!(self.peek(), Token::LeftBrace) { 425 self.skip_at_rule_body(); 426 return None; 427 } 428 self.advance(); 429 430 // Parse keyframe blocks 431 let mut keyframes = Vec::new(); 432 loop { 433 self.skip_whitespace(); 434 if self.is_eof() { 435 break; 436 } 437 if matches!(self.peek(), Token::RightBrace) { 438 self.advance(); 439 break; 440 } 441 442 if let Some(kf) = self.parse_keyframe_block() { 443 keyframes.push(kf); 444 } 445 } 446 447 Some(Rule::Keyframes(KeyframesRule { name, keyframes })) 448 } 449 450 fn parse_font_face_rule(&mut self) -> Option<Rule> { 451 self.skip_whitespace(); 452 453 // Expect `{` 454 if !matches!(self.peek(), Token::LeftBrace) { 455 self.skip_at_rule_body(); 456 return None; 457 } 458 self.advance(); 459 460 // Parse descriptors inside the block. 461 let declarations = self.parse_declaration_list_until_brace(); 462 463 // Consume `}` 464 if matches!(self.peek(), Token::RightBrace) { 465 self.advance(); 466 } 467 468 // Extract font-family descriptor (required). 469 let mut family = None; 470 let mut sources = Vec::new(); 471 let mut weight: u16 = 400; 472 let mut style = FontFaceStyle::Normal; 473 let mut display = FontDisplay::Auto; 474 475 for decl in &declarations { 476 match decl.property.as_str() { 477 "font-family" => { 478 family = extract_font_face_family(&decl.value); 479 } 480 "src" => { 481 sources = parse_font_face_src(&decl.value); 482 } 483 "font-weight" => { 484 weight = parse_font_face_weight(&decl.value); 485 } 486 "font-style" => { 487 style = parse_font_face_style(&decl.value); 488 } 489 "font-display" => { 490 display = parse_font_face_display(&decl.value); 491 } 492 _ => {} 493 } 494 } 495 496 let family = family?; 497 if sources.is_empty() { 498 return None; 499 } 500 501 Some(Rule::FontFace(FontFaceRule { 502 family, 503 sources, 504 weight, 505 style, 506 display, 507 })) 508 } 509 510 fn parse_keyframe_block(&mut self) -> Option<Keyframe> { 511 // Parse keyframe selectors (comma-separated: `from`, `to`, or percentages) 512 let mut selectors = Vec::new(); 513 loop { 514 self.skip_whitespace(); 515 match self.peek() { 516 Token::LeftBrace | Token::Eof | Token::RightBrace => break, 517 Token::Comma => { 518 self.advance(); 519 continue; 520 } 521 Token::Ident(s) if s.eq_ignore_ascii_case("from") => { 522 selectors.push(KeyframeSelector::From); 523 self.advance(); 524 } 525 Token::Ident(s) if s.eq_ignore_ascii_case("to") => { 526 selectors.push(KeyframeSelector::To); 527 self.advance(); 528 } 529 Token::Percentage(_) => { 530 if let Token::Percentage(p) = self.advance() { 531 selectors.push(KeyframeSelector::Percentage(p)); 532 } 533 } 534 Token::Number(n, _) if *n == 0.0 => { 535 // Allow bare `0` as 0% 536 self.advance(); 537 selectors.push(KeyframeSelector::Percentage(0.0)); 538 // Check for optional `%` sign 539 if matches!(self.peek(), Token::Delim('%')) { 540 self.advance(); 541 } 542 } 543 _ => { 544 // Unknown token in keyframe selector — skip to next block 545 self.skip_at_rule_body(); 546 return None; 547 } 548 } 549 } 550 551 if selectors.is_empty() { 552 return None; 553 } 554 555 // Expect `{` 556 if !matches!(self.peek(), Token::LeftBrace) { 557 return None; 558 } 559 self.advance(); 560 561 let declarations = self.parse_declaration_list_until_brace(); 562 563 // Consume `}` 564 if matches!(self.peek(), Token::RightBrace) { 565 self.advance(); 566 } 567 568 Some(Keyframe { 569 selectors, 570 declarations, 571 }) 572 } 573 574 fn skip_at_rule_body(&mut self) { 575 let mut brace_depth = 0; 576 loop { 577 match self.peek() { 578 Token::Eof => return, 579 Token::Semicolon if brace_depth == 0 => { 580 self.advance(); 581 return; 582 } 583 Token::LeftBrace => { 584 brace_depth += 1; 585 self.advance(); 586 } 587 Token::RightBrace => { 588 if brace_depth == 0 { 589 self.advance(); 590 return; 591 } 592 brace_depth -= 1; 593 self.advance(); 594 if brace_depth == 0 { 595 return; 596 } 597 } 598 _ => { 599 self.advance(); 600 } 601 } 602 } 603 } 604 605 fn skip_to_semicolon(&mut self) { 606 loop { 607 match self.peek() { 608 Token::Eof | Token::Semicolon => { 609 if matches!(self.peek(), Token::Semicolon) { 610 self.advance(); 611 } 612 return; 613 } 614 _ => { 615 self.advance(); 616 } 617 } 618 } 619 } 620 621 // -- Style rules -------------------------------------------------------- 622 623 fn parse_style_rule(&mut self) -> Option<StyleRule> { 624 let selectors = self.parse_selector_list(); 625 626 // Expect `{` 627 self.skip_whitespace(); 628 if !matches!(self.peek(), Token::LeftBrace) { 629 // Error recovery: skip to end of block or next rule 630 self.skip_at_rule_body(); 631 return None; 632 } 633 self.advance(); 634 635 let declarations = self.parse_declaration_list_until_brace(); 636 637 // Consume `}` 638 if matches!(self.peek(), Token::RightBrace) { 639 self.advance(); 640 } 641 642 if selectors.selectors.is_empty() { 643 return None; 644 } 645 646 Some(StyleRule { 647 selectors, 648 declarations, 649 }) 650 } 651 652 // -- Selector parsing --------------------------------------------------- 653 654 fn parse_selector_list(&mut self) -> SelectorList { 655 let mut selectors = Vec::new(); 656 self.skip_whitespace(); 657 658 if let Some(sel) = self.parse_selector() { 659 selectors.push(sel); 660 } 661 662 loop { 663 self.skip_whitespace(); 664 if !matches!(self.peek(), Token::Comma) { 665 break; 666 } 667 self.advance(); // consume comma 668 self.skip_whitespace(); 669 if let Some(sel) = self.parse_selector() { 670 selectors.push(sel); 671 } 672 } 673 674 SelectorList { selectors } 675 } 676 677 fn parse_selector(&mut self) -> Option<Selector> { 678 let mut components = Vec::new(); 679 let mut last_was_compound = false; 680 let mut had_whitespace = false; 681 682 loop { 683 // Check for end of selector 684 match self.peek() { 685 Token::Comma 686 | Token::LeftBrace 687 | Token::RightBrace 688 | Token::Semicolon 689 | Token::Eof => break, 690 Token::Whitespace => { 691 had_whitespace = true; 692 self.advance(); 693 continue; 694 } 695 _ => {} 696 } 697 698 // Check for explicit combinator 699 let combinator = match self.peek() { 700 Token::Delim('>') => { 701 self.advance(); 702 Some(Combinator::Child) 703 } 704 Token::Delim('+') => { 705 self.advance(); 706 Some(Combinator::AdjacentSibling) 707 } 708 Token::Delim('~') => { 709 self.advance(); 710 Some(Combinator::GeneralSibling) 711 } 712 _ => None, 713 }; 714 715 if let Some(comb) = combinator { 716 if last_was_compound { 717 components.push(SelectorComponent::Combinator(comb)); 718 last_was_compound = false; 719 had_whitespace = false; 720 } 721 self.skip_whitespace(); 722 continue; 723 } 724 725 // Implicit descendant combinator if there was whitespace 726 if had_whitespace && last_was_compound { 727 components.push(SelectorComponent::Combinator(Combinator::Descendant)); 728 had_whitespace = false; 729 } 730 731 // Parse compound selector 732 if let Some(compound) = self.parse_compound_selector() { 733 components.push(SelectorComponent::Compound(compound)); 734 last_was_compound = true; 735 } else { 736 break; 737 } 738 } 739 740 if components.is_empty() { 741 None 742 } else { 743 Some(Selector { components }) 744 } 745 } 746 747 fn parse_compound_selector(&mut self) -> Option<CompoundSelector> { 748 let mut simple = Vec::new(); 749 750 loop { 751 match self.peek() { 752 // Type or universal 753 Token::Ident(_) if simple.is_empty() || !has_type_or_universal(&simple) => { 754 if let Token::Ident(name) = self.advance() { 755 simple.push(SimpleSelector::Type(name.to_ascii_lowercase())); 756 } 757 } 758 Token::Delim('*') if simple.is_empty() || !has_type_or_universal(&simple) => { 759 self.advance(); 760 simple.push(SimpleSelector::Universal); 761 } 762 // Class 763 Token::Delim('.') => { 764 self.advance(); 765 match self.advance() { 766 Token::Ident(name) => simple.push(SimpleSelector::Class(name)), 767 _ => break, 768 } 769 } 770 // ID 771 Token::Hash(_, HashType::Id) => { 772 if let Token::Hash(name, _) = self.advance() { 773 simple.push(SimpleSelector::Id(name)); 774 } 775 } 776 // Attribute 777 Token::LeftBracket => { 778 self.advance(); 779 if let Some(attr) = self.parse_attribute_selector() { 780 simple.push(SimpleSelector::Attribute(attr)); 781 } 782 } 783 // Pseudo-class 784 Token::Colon => { 785 self.advance(); 786 match self.advance() { 787 Token::Ident(name) => { 788 simple.push(SimpleSelector::PseudoClass(name.to_ascii_lowercase())) 789 } 790 Token::Function(name) => { 791 // Parse pseudo-class with arguments: skip to closing paren 792 let mut pname = name.to_ascii_lowercase(); 793 pname.push('('); 794 let mut depth = 1; 795 loop { 796 match self.peek() { 797 Token::Eof => break, 798 Token::LeftParen => { 799 depth += 1; 800 pname.push('('); 801 self.advance(); 802 } 803 Token::RightParen => { 804 depth -= 1; 805 if depth == 0 { 806 self.advance(); 807 break; 808 } 809 pname.push(')'); 810 self.advance(); 811 } 812 _ => { 813 let tok = self.advance(); 814 pname.push_str(&token_to_string(&tok)); 815 } 816 } 817 } 818 pname.push(')'); 819 simple.push(SimpleSelector::PseudoClass(pname)); 820 } 821 _ => break, 822 } 823 } 824 // Ident after already having a type selector → new compound starts 825 Token::Ident(_) => break, 826 _ => break, 827 } 828 } 829 830 if simple.is_empty() { 831 None 832 } else { 833 Some(CompoundSelector { simple }) 834 } 835 } 836 837 fn parse_attribute_selector(&mut self) -> Option<AttributeSelector> { 838 self.skip_whitespace(); 839 840 let name = match self.advance() { 841 Token::Ident(name) => name.to_ascii_lowercase(), 842 _ => { 843 self.skip_to_bracket_close(); 844 return None; 845 } 846 }; 847 848 self.skip_whitespace(); 849 850 // Check for close bracket (presence-only selector) 851 if matches!(self.peek(), Token::RightBracket) { 852 self.advance(); 853 return Some(AttributeSelector { 854 name, 855 op: None, 856 value: None, 857 }); 858 } 859 860 // Parse operator 861 let op = match self.peek() { 862 Token::Delim('=') => { 863 self.advance(); 864 AttributeOp::Exact 865 } 866 Token::Delim('~') => { 867 self.advance(); 868 if matches!(self.peek(), Token::Delim('=')) { 869 self.advance(); 870 } 871 AttributeOp::Includes 872 } 873 Token::Delim('|') => { 874 self.advance(); 875 if matches!(self.peek(), Token::Delim('=')) { 876 self.advance(); 877 } 878 AttributeOp::DashMatch 879 } 880 Token::Delim('^') => { 881 self.advance(); 882 if matches!(self.peek(), Token::Delim('=')) { 883 self.advance(); 884 } 885 AttributeOp::Prefix 886 } 887 Token::Delim('$') => { 888 self.advance(); 889 if matches!(self.peek(), Token::Delim('=')) { 890 self.advance(); 891 } 892 AttributeOp::Suffix 893 } 894 Token::Delim('*') => { 895 self.advance(); 896 if matches!(self.peek(), Token::Delim('=')) { 897 self.advance(); 898 } 899 AttributeOp::Substring 900 } 901 _ => { 902 self.skip_to_bracket_close(); 903 return None; 904 } 905 }; 906 907 self.skip_whitespace(); 908 909 // Parse value 910 let value = match self.advance() { 911 Token::Ident(v) => v, 912 Token::String(v) => v, 913 _ => { 914 self.skip_to_bracket_close(); 915 return None; 916 } 917 }; 918 919 self.skip_whitespace(); 920 921 // Consume closing bracket 922 if matches!(self.peek(), Token::RightBracket) { 923 self.advance(); 924 } 925 926 Some(AttributeSelector { 927 name, 928 op: Some(op), 929 value: Some(value), 930 }) 931 } 932 933 fn skip_to_bracket_close(&mut self) { 934 loop { 935 match self.peek() { 936 Token::RightBracket | Token::Eof => { 937 if matches!(self.peek(), Token::RightBracket) { 938 self.advance(); 939 } 940 return; 941 } 942 _ => { 943 self.advance(); 944 } 945 } 946 } 947 } 948 949 // -- Declaration parsing ------------------------------------------------ 950 951 fn parse_declaration_list(&mut self) -> Vec<Declaration> { 952 let mut declarations = Vec::new(); 953 loop { 954 self.skip_whitespace(); 955 if self.is_eof() { 956 break; 957 } 958 if matches!(self.peek(), Token::Semicolon) { 959 self.advance(); 960 continue; 961 } 962 if let Some(decl) = self.parse_declaration() { 963 declarations.push(decl); 964 } 965 } 966 declarations 967 } 968 969 fn parse_declaration_list_until_brace(&mut self) -> Vec<Declaration> { 970 let mut declarations = Vec::new(); 971 loop { 972 self.skip_whitespace(); 973 if self.is_eof() || matches!(self.peek(), Token::RightBrace) { 974 break; 975 } 976 if matches!(self.peek(), Token::Semicolon) { 977 self.advance(); 978 continue; 979 } 980 if let Some(decl) = self.parse_declaration() { 981 declarations.push(decl); 982 } 983 } 984 declarations 985 } 986 987 fn parse_declaration(&mut self) -> Option<Declaration> { 988 // Property name 989 let property = match self.peek() { 990 Token::Ident(_) => { 991 if let Token::Ident(name) = self.advance() { 992 name.to_ascii_lowercase() 993 } else { 994 unreachable!() 995 } 996 } 997 _ => { 998 // Error recovery: skip to next semicolon or closing brace 999 self.skip_declaration_error(); 1000 return None; 1001 } 1002 }; 1003 1004 self.skip_whitespace(); 1005 1006 // Expect colon 1007 if !matches!(self.peek(), Token::Colon) { 1008 self.skip_declaration_error(); 1009 return None; 1010 } 1011 self.advance(); 1012 1013 self.skip_whitespace(); 1014 1015 // Parse value 1016 let (value, important) = self.parse_declaration_value(); 1017 1018 if value.is_empty() { 1019 return None; 1020 } 1021 1022 Some(Declaration { 1023 property, 1024 value, 1025 important, 1026 }) 1027 } 1028 1029 fn parse_declaration_value(&mut self) -> (Vec<ComponentValue>, bool) { 1030 let mut values = Vec::new(); 1031 let mut important = false; 1032 1033 loop { 1034 match self.peek() { 1035 Token::Semicolon | Token::RightBrace | Token::Eof => break, 1036 Token::Whitespace => { 1037 self.advance(); 1038 // Only add whitespace if there are already values and we're 1039 // not at the end of the declaration 1040 if !values.is_empty() 1041 && !matches!( 1042 self.peek(), 1043 Token::Semicolon | Token::RightBrace | Token::Eof 1044 ) 1045 { 1046 values.push(ComponentValue::Whitespace); 1047 } 1048 } 1049 Token::Delim('!') => { 1050 self.advance(); 1051 self.skip_whitespace(); 1052 if let Token::Ident(ref s) = self.peek() { 1053 if s.eq_ignore_ascii_case("important") { 1054 important = true; 1055 self.advance(); 1056 } 1057 } 1058 } 1059 _ => { 1060 if let Some(cv) = self.parse_component_value() { 1061 values.push(cv); 1062 } 1063 } 1064 } 1065 } 1066 1067 // Trim trailing whitespace 1068 while matches!(values.last(), Some(ComponentValue::Whitespace)) { 1069 values.pop(); 1070 } 1071 1072 (values, important) 1073 } 1074 1075 fn parse_component_value(&mut self) -> Option<ComponentValue> { 1076 match self.advance() { 1077 Token::Ident(s) => Some(ComponentValue::Ident(s)), 1078 Token::String(s) => Some(ComponentValue::String(s)), 1079 Token::Number(n, t) => Some(ComponentValue::Number(n, t)), 1080 Token::Percentage(n) => Some(ComponentValue::Percentage(n)), 1081 Token::Dimension(n, t, u) => Some(ComponentValue::Dimension(n, t, u)), 1082 Token::Hash(s, ht) => Some(ComponentValue::Hash(s, ht)), 1083 Token::Comma => Some(ComponentValue::Comma), 1084 Token::Delim(c) => Some(ComponentValue::Delim(c)), 1085 Token::Function(name) => { 1086 let args = self.parse_function_args(); 1087 Some(ComponentValue::Function(name, args)) 1088 } 1089 _ => None, 1090 } 1091 } 1092 1093 fn parse_function_args(&mut self) -> Vec<ComponentValue> { 1094 let mut args = Vec::new(); 1095 loop { 1096 match self.peek() { 1097 Token::RightParen | Token::Eof => { 1098 if matches!(self.peek(), Token::RightParen) { 1099 self.advance(); 1100 } 1101 break; 1102 } 1103 Token::Whitespace => { 1104 self.advance(); 1105 if !args.is_empty() && !matches!(self.peek(), Token::RightParen | Token::Eof) { 1106 args.push(ComponentValue::Whitespace); 1107 } 1108 } 1109 _ => { 1110 if let Some(cv) = self.parse_component_value() { 1111 args.push(cv); 1112 } 1113 } 1114 } 1115 } 1116 args 1117 } 1118 1119 fn skip_declaration_error(&mut self) { 1120 loop { 1121 match self.peek() { 1122 Token::Semicolon => { 1123 self.advance(); 1124 return; 1125 } 1126 Token::RightBrace | Token::Eof => return, 1127 _ => { 1128 self.advance(); 1129 } 1130 } 1131 } 1132 } 1133} 1134 1135// --------------------------------------------------------------------------- 1136// Helpers 1137// --------------------------------------------------------------------------- 1138 1139fn has_type_or_universal(selectors: &[SimpleSelector]) -> bool { 1140 selectors 1141 .iter() 1142 .any(|s| matches!(s, SimpleSelector::Type(_) | SimpleSelector::Universal)) 1143} 1144 1145fn token_to_string(token: &Token) -> String { 1146 match token { 1147 Token::Ident(s) => s.clone(), 1148 Token::Function(s) => format!("{s}("), 1149 Token::AtKeyword(s) => format!("@{s}"), 1150 Token::Hash(s, _) => format!("#{s}"), 1151 Token::String(s) => format!("\"{s}\""), 1152 Token::Url(s) => format!("url({s})"), 1153 Token::Number(n, _) => format!("{n}"), 1154 Token::Percentage(n) => format!("{n}%"), 1155 Token::Dimension(n, _, u) => format!("{n}{u}"), 1156 Token::Whitespace => " ".to_string(), 1157 Token::Colon => ":".to_string(), 1158 Token::Semicolon => ";".to_string(), 1159 Token::Comma => ",".to_string(), 1160 Token::LeftBracket => "[".to_string(), 1161 Token::RightBracket => "]".to_string(), 1162 Token::LeftParen => "(".to_string(), 1163 Token::RightParen => ")".to_string(), 1164 Token::LeftBrace => "{".to_string(), 1165 Token::RightBrace => "}".to_string(), 1166 Token::Delim(c) => c.to_string(), 1167 Token::Cdo => "<!--".to_string(), 1168 Token::Cdc => "-->".to_string(), 1169 Token::BadString | Token::BadUrl | Token::Eof => String::new(), 1170 } 1171} 1172 1173// --------------------------------------------------------------------------- 1174// @font-face descriptor helpers 1175// --------------------------------------------------------------------------- 1176 1177/// Extract the family name from a `font-family` descriptor value. 1178fn extract_font_face_family(values: &[ComponentValue]) -> Option<String> { 1179 for v in values { 1180 match v { 1181 ComponentValue::String(s) => return Some(s.clone()), 1182 ComponentValue::Ident(s) => return Some(s.clone()), 1183 _ => {} 1184 } 1185 } 1186 None 1187} 1188 1189/// Parse the `src` descriptor into a list of font sources. 1190/// 1191/// Supports `url("...")` and `url("...") format("...")` syntax. 1192/// Multiple sources are comma-separated. 1193fn parse_font_face_src(values: &[ComponentValue]) -> Vec<FontFaceSource> { 1194 let mut sources = Vec::new(); 1195 let mut i = 0; 1196 1197 while i < values.len() { 1198 match &values[i] { 1199 ComponentValue::Function(name, args) if name.eq_ignore_ascii_case("url") => { 1200 let url = extract_string_from_args(args); 1201 i += 1; 1202 // Check for optional format() hint. 1203 let format = skip_ws_and_parse_format(values, &mut i); 1204 if let Some(url) = url { 1205 sources.push(FontFaceSource { url, format }); 1206 } 1207 } 1208 ComponentValue::Comma => { 1209 i += 1; 1210 } 1211 _ => { 1212 i += 1; 1213 } 1214 } 1215 } 1216 1217 sources 1218} 1219 1220/// Extract a string or ident from function arguments. 1221fn extract_string_from_args(args: &[ComponentValue]) -> Option<String> { 1222 for arg in args { 1223 match arg { 1224 ComponentValue::String(s) => return Some(s.clone()), 1225 ComponentValue::Ident(s) => return Some(s.clone()), 1226 _ => {} 1227 } 1228 } 1229 None 1230} 1231 1232/// Skip whitespace tokens and try to parse a `format()` function. 1233fn skip_ws_and_parse_format(values: &[ComponentValue], i: &mut usize) -> Option<String> { 1234 // Skip whitespace. 1235 while *i < values.len() && matches!(values[*i], ComponentValue::Whitespace) { 1236 *i += 1; 1237 } 1238 // Check for format(). 1239 if *i < values.len() { 1240 if let ComponentValue::Function(name, args) = &values[*i] { 1241 if name.eq_ignore_ascii_case("format") { 1242 *i += 1; 1243 return extract_string_from_args(args); 1244 } 1245 } 1246 } 1247 None 1248} 1249 1250/// Parse `font-weight` descriptor: `normal`, `bold`, or numeric (100–900). 1251fn parse_font_face_weight(values: &[ComponentValue]) -> u16 { 1252 for v in values { 1253 match v { 1254 ComponentValue::Ident(s) if s.eq_ignore_ascii_case("normal") => return 400, 1255 ComponentValue::Ident(s) if s.eq_ignore_ascii_case("bold") => return 700, 1256 ComponentValue::Number(n, _) => { 1257 let w = *n as u16; 1258 return w.clamp(1, 1000); 1259 } 1260 _ => {} 1261 } 1262 } 1263 400 1264} 1265 1266/// Parse `font-style` descriptor: `normal`, `italic`, `oblique`. 1267fn parse_font_face_style(values: &[ComponentValue]) -> FontFaceStyle { 1268 for v in values { 1269 if let ComponentValue::Ident(s) = v { 1270 if s.eq_ignore_ascii_case("italic") { 1271 return FontFaceStyle::Italic; 1272 } 1273 if s.eq_ignore_ascii_case("oblique") { 1274 return FontFaceStyle::Oblique; 1275 } 1276 if s.eq_ignore_ascii_case("normal") { 1277 return FontFaceStyle::Normal; 1278 } 1279 } 1280 } 1281 FontFaceStyle::Normal 1282} 1283 1284/// Parse `font-display` descriptor. 1285fn parse_font_face_display(values: &[ComponentValue]) -> FontDisplay { 1286 for v in values { 1287 if let ComponentValue::Ident(s) = v { 1288 match s.to_ascii_lowercase().as_str() { 1289 "auto" => return FontDisplay::Auto, 1290 "block" => return FontDisplay::Block, 1291 "swap" => return FontDisplay::Swap, 1292 "fallback" => return FontDisplay::Fallback, 1293 "optional" => return FontDisplay::Optional, 1294 _ => {} 1295 } 1296 } 1297 } 1298 FontDisplay::Auto 1299} 1300 1301// --------------------------------------------------------------------------- 1302// Tests 1303// --------------------------------------------------------------------------- 1304 1305#[cfg(test)] 1306mod tests { 1307 use super::*; 1308 1309 // -- Selector tests ----------------------------------------------------- 1310 1311 #[test] 1312 fn test_type_selector() { 1313 let ss = Parser::parse("div { }"); 1314 assert_eq!(ss.rules.len(), 1); 1315 let rule = match &ss.rules[0] { 1316 Rule::Style(r) => r, 1317 _ => panic!("expected style rule"), 1318 }; 1319 assert_eq!(rule.selectors.selectors.len(), 1); 1320 let sel = &rule.selectors.selectors[0]; 1321 assert_eq!(sel.components.len(), 1); 1322 match &sel.components[0] { 1323 SelectorComponent::Compound(c) => { 1324 assert_eq!(c.simple.len(), 1); 1325 assert_eq!(c.simple[0], SimpleSelector::Type("div".into())); 1326 } 1327 _ => panic!("expected compound"), 1328 } 1329 } 1330 1331 #[test] 1332 fn test_universal_selector() { 1333 let ss = Parser::parse("* { }"); 1334 let rule = match &ss.rules[0] { 1335 Rule::Style(r) => r, 1336 _ => panic!("expected style rule"), 1337 }; 1338 let sel = &rule.selectors.selectors[0]; 1339 match &sel.components[0] { 1340 SelectorComponent::Compound(c) => { 1341 assert_eq!(c.simple[0], SimpleSelector::Universal); 1342 } 1343 _ => panic!("expected compound"), 1344 } 1345 } 1346 1347 #[test] 1348 fn test_class_selector() { 1349 let ss = Parser::parse(".foo { }"); 1350 let rule = match &ss.rules[0] { 1351 Rule::Style(r) => r, 1352 _ => panic!("expected style rule"), 1353 }; 1354 let sel = &rule.selectors.selectors[0]; 1355 match &sel.components[0] { 1356 SelectorComponent::Compound(c) => { 1357 assert_eq!(c.simple[0], SimpleSelector::Class("foo".into())); 1358 } 1359 _ => panic!("expected compound"), 1360 } 1361 } 1362 1363 #[test] 1364 fn test_id_selector() { 1365 let ss = Parser::parse("#main { }"); 1366 let rule = match &ss.rules[0] { 1367 Rule::Style(r) => r, 1368 _ => panic!("expected style rule"), 1369 }; 1370 let sel = &rule.selectors.selectors[0]; 1371 match &sel.components[0] { 1372 SelectorComponent::Compound(c) => { 1373 assert_eq!(c.simple[0], SimpleSelector::Id("main".into())); 1374 } 1375 _ => panic!("expected compound"), 1376 } 1377 } 1378 1379 #[test] 1380 fn test_compound_selector() { 1381 let ss = Parser::parse("div.foo#bar { }"); 1382 let rule = match &ss.rules[0] { 1383 Rule::Style(r) => r, 1384 _ => panic!("expected style rule"), 1385 }; 1386 let sel = &rule.selectors.selectors[0]; 1387 match &sel.components[0] { 1388 SelectorComponent::Compound(c) => { 1389 assert_eq!(c.simple.len(), 3); 1390 assert_eq!(c.simple[0], SimpleSelector::Type("div".into())); 1391 assert_eq!(c.simple[1], SimpleSelector::Class("foo".into())); 1392 assert_eq!(c.simple[2], SimpleSelector::Id("bar".into())); 1393 } 1394 _ => panic!("expected compound"), 1395 } 1396 } 1397 1398 #[test] 1399 fn test_selector_list() { 1400 let ss = Parser::parse("h1, h2, h3 { }"); 1401 let rule = match &ss.rules[0] { 1402 Rule::Style(r) => r, 1403 _ => panic!("expected style rule"), 1404 }; 1405 assert_eq!(rule.selectors.selectors.len(), 3); 1406 } 1407 1408 #[test] 1409 fn test_descendant_combinator() { 1410 let ss = Parser::parse("div p { }"); 1411 let rule = match &ss.rules[0] { 1412 Rule::Style(r) => r, 1413 _ => panic!("expected style rule"), 1414 }; 1415 let sel = &rule.selectors.selectors[0]; 1416 assert_eq!(sel.components.len(), 3); 1417 assert!(matches!( 1418 sel.components[1], 1419 SelectorComponent::Combinator(Combinator::Descendant) 1420 )); 1421 } 1422 1423 #[test] 1424 fn test_child_combinator() { 1425 let ss = Parser::parse("div > p { }"); 1426 let rule = match &ss.rules[0] { 1427 Rule::Style(r) => r, 1428 _ => panic!("expected style rule"), 1429 }; 1430 let sel = &rule.selectors.selectors[0]; 1431 assert_eq!(sel.components.len(), 3); 1432 assert!(matches!( 1433 sel.components[1], 1434 SelectorComponent::Combinator(Combinator::Child) 1435 )); 1436 } 1437 1438 #[test] 1439 fn test_adjacent_sibling_combinator() { 1440 let ss = Parser::parse("h1 + p { }"); 1441 let rule = match &ss.rules[0] { 1442 Rule::Style(r) => r, 1443 _ => panic!("expected style rule"), 1444 }; 1445 let sel = &rule.selectors.selectors[0]; 1446 assert!(matches!( 1447 sel.components[1], 1448 SelectorComponent::Combinator(Combinator::AdjacentSibling) 1449 )); 1450 } 1451 1452 #[test] 1453 fn test_general_sibling_combinator() { 1454 let ss = Parser::parse("h1 ~ p { }"); 1455 let rule = match &ss.rules[0] { 1456 Rule::Style(r) => r, 1457 _ => panic!("expected style rule"), 1458 }; 1459 let sel = &rule.selectors.selectors[0]; 1460 assert!(matches!( 1461 sel.components[1], 1462 SelectorComponent::Combinator(Combinator::GeneralSibling) 1463 )); 1464 } 1465 1466 #[test] 1467 fn test_attribute_presence() { 1468 let ss = Parser::parse("[disabled] { }"); 1469 let rule = match &ss.rules[0] { 1470 Rule::Style(r) => r, 1471 _ => panic!("expected style rule"), 1472 }; 1473 let sel = &rule.selectors.selectors[0]; 1474 match &sel.components[0] { 1475 SelectorComponent::Compound(c) => match &c.simple[0] { 1476 SimpleSelector::Attribute(attr) => { 1477 assert_eq!(attr.name, "disabled"); 1478 assert!(attr.op.is_none()); 1479 assert!(attr.value.is_none()); 1480 } 1481 _ => panic!("expected attribute selector"), 1482 }, 1483 _ => panic!("expected compound"), 1484 } 1485 } 1486 1487 #[test] 1488 fn test_attribute_exact() { 1489 let ss = Parser::parse("[type=\"text\"] { }"); 1490 let rule = match &ss.rules[0] { 1491 Rule::Style(r) => r, 1492 _ => panic!("expected style rule"), 1493 }; 1494 let sel = &rule.selectors.selectors[0]; 1495 match &sel.components[0] { 1496 SelectorComponent::Compound(c) => match &c.simple[0] { 1497 SimpleSelector::Attribute(attr) => { 1498 assert_eq!(attr.name, "type"); 1499 assert_eq!(attr.op, Some(AttributeOp::Exact)); 1500 assert_eq!(attr.value, Some("text".into())); 1501 } 1502 _ => panic!("expected attribute selector"), 1503 }, 1504 _ => panic!("expected compound"), 1505 } 1506 } 1507 1508 #[test] 1509 fn test_attribute_operators() { 1510 let ops = vec![ 1511 ("[a~=b] { }", AttributeOp::Includes), 1512 ("[a|=b] { }", AttributeOp::DashMatch), 1513 ("[a^=b] { }", AttributeOp::Prefix), 1514 ("[a$=b] { }", AttributeOp::Suffix), 1515 ("[a*=b] { }", AttributeOp::Substring), 1516 ]; 1517 for (input, expected_op) in ops { 1518 let ss = Parser::parse(input); 1519 let rule = match &ss.rules[0] { 1520 Rule::Style(r) => r, 1521 _ => panic!("expected style rule"), 1522 }; 1523 let sel = &rule.selectors.selectors[0]; 1524 match &sel.components[0] { 1525 SelectorComponent::Compound(c) => match &c.simple[0] { 1526 SimpleSelector::Attribute(attr) => { 1527 assert_eq!(attr.op, Some(expected_op), "failed for {input}"); 1528 } 1529 _ => panic!("expected attribute selector"), 1530 }, 1531 _ => panic!("expected compound"), 1532 } 1533 } 1534 } 1535 1536 #[test] 1537 fn test_pseudo_class() { 1538 let ss = Parser::parse("a:hover { }"); 1539 let rule = match &ss.rules[0] { 1540 Rule::Style(r) => r, 1541 _ => panic!("expected style rule"), 1542 }; 1543 let sel = &rule.selectors.selectors[0]; 1544 match &sel.components[0] { 1545 SelectorComponent::Compound(c) => { 1546 assert_eq!(c.simple.len(), 2); 1547 assert_eq!(c.simple[0], SimpleSelector::Type("a".into())); 1548 assert_eq!(c.simple[1], SimpleSelector::PseudoClass("hover".into())); 1549 } 1550 _ => panic!("expected compound"), 1551 } 1552 } 1553 1554 #[test] 1555 fn test_pseudo_class_first_child() { 1556 let ss = Parser::parse("p:first-child { }"); 1557 let rule = match &ss.rules[0] { 1558 Rule::Style(r) => r, 1559 _ => panic!("expected style rule"), 1560 }; 1561 let sel = &rule.selectors.selectors[0]; 1562 match &sel.components[0] { 1563 SelectorComponent::Compound(c) => { 1564 assert_eq!( 1565 c.simple[1], 1566 SimpleSelector::PseudoClass("first-child".into()) 1567 ); 1568 } 1569 _ => panic!("expected compound"), 1570 } 1571 } 1572 1573 // -- Declaration tests -------------------------------------------------- 1574 1575 #[test] 1576 fn test_simple_declaration() { 1577 let ss = Parser::parse("p { color: red; }"); 1578 let rule = match &ss.rules[0] { 1579 Rule::Style(r) => r, 1580 _ => panic!("expected style rule"), 1581 }; 1582 assert_eq!(rule.declarations.len(), 1); 1583 assert_eq!(rule.declarations[0].property, "color"); 1584 assert_eq!(rule.declarations[0].value.len(), 1); 1585 assert_eq!( 1586 rule.declarations[0].value[0], 1587 ComponentValue::Ident("red".into()) 1588 ); 1589 assert!(!rule.declarations[0].important); 1590 } 1591 1592 #[test] 1593 fn test_multiple_declarations() { 1594 let ss = Parser::parse("p { color: red; font-size: 16px; }"); 1595 let rule = match &ss.rules[0] { 1596 Rule::Style(r) => r, 1597 _ => panic!("expected style rule"), 1598 }; 1599 assert_eq!(rule.declarations.len(), 2); 1600 assert_eq!(rule.declarations[0].property, "color"); 1601 assert_eq!(rule.declarations[1].property, "font-size"); 1602 } 1603 1604 #[test] 1605 fn test_important_declaration() { 1606 let ss = Parser::parse("p { color: red !important; }"); 1607 let rule = match &ss.rules[0] { 1608 Rule::Style(r) => r, 1609 _ => panic!("expected style rule"), 1610 }; 1611 assert!(rule.declarations[0].important); 1612 } 1613 1614 #[test] 1615 fn test_declaration_with_function() { 1616 let ss = Parser::parse("p { color: rgb(255, 0, 0); }"); 1617 let rule = match &ss.rules[0] { 1618 Rule::Style(r) => r, 1619 _ => panic!("expected style rule"), 1620 }; 1621 assert_eq!(rule.declarations[0].property, "color"); 1622 match &rule.declarations[0].value[0] { 1623 ComponentValue::Function(name, args) => { 1624 assert_eq!(name, "rgb"); 1625 // 255, 0, 0 → Number, Comma, WS, Number, Comma, WS, Number 1626 assert!(!args.is_empty()); 1627 } 1628 _ => panic!("expected function value"), 1629 } 1630 } 1631 1632 #[test] 1633 fn test_declaration_with_hash_color() { 1634 let ss = Parser::parse("p { color: #ff0000; }"); 1635 let rule = match &ss.rules[0] { 1636 Rule::Style(r) => r, 1637 _ => panic!("expected style rule"), 1638 }; 1639 assert_eq!( 1640 rule.declarations[0].value[0], 1641 ComponentValue::Hash("ff0000".into(), HashType::Id) 1642 ); 1643 } 1644 1645 #[test] 1646 fn test_declaration_with_dimension() { 1647 let ss = Parser::parse("p { margin: 10px; }"); 1648 let rule = match &ss.rules[0] { 1649 Rule::Style(r) => r, 1650 _ => panic!("expected style rule"), 1651 }; 1652 assert_eq!( 1653 rule.declarations[0].value[0], 1654 ComponentValue::Dimension(10.0, NumericType::Integer, "px".into()) 1655 ); 1656 } 1657 1658 #[test] 1659 fn test_declaration_with_percentage() { 1660 let ss = Parser::parse("p { width: 50%; }"); 1661 let rule = match &ss.rules[0] { 1662 Rule::Style(r) => r, 1663 _ => panic!("expected style rule"), 1664 }; 1665 assert_eq!( 1666 rule.declarations[0].value[0], 1667 ComponentValue::Percentage(50.0) 1668 ); 1669 } 1670 1671 #[test] 1672 fn test_parse_inline_style() { 1673 let decls = Parser::parse_declarations("color: red; font-size: 16px"); 1674 assert_eq!(decls.len(), 2); 1675 assert_eq!(decls[0].property, "color"); 1676 assert_eq!(decls[1].property, "font-size"); 1677 } 1678 1679 // -- Error recovery tests ----------------------------------------------- 1680 1681 #[test] 1682 fn test_invalid_declaration_skipped() { 1683 let ss = Parser::parse("p { ??? ; color: red; }"); 1684 let rule = match &ss.rules[0] { 1685 Rule::Style(r) => r, 1686 _ => panic!("expected style rule"), 1687 }; 1688 // The invalid declaration should be skipped, color should remain 1689 assert_eq!(rule.declarations.len(), 1); 1690 assert_eq!(rule.declarations[0].property, "color"); 1691 } 1692 1693 #[test] 1694 fn test_missing_colon_skipped() { 1695 let ss = Parser::parse("p { color red; font-size: 16px; }"); 1696 let rule = match &ss.rules[0] { 1697 Rule::Style(r) => r, 1698 _ => panic!("expected style rule"), 1699 }; 1700 assert_eq!(rule.declarations.len(), 1); 1701 assert_eq!(rule.declarations[0].property, "font-size"); 1702 } 1703 1704 #[test] 1705 fn test_empty_stylesheet() { 1706 let ss = Parser::parse(""); 1707 assert_eq!(ss.rules.len(), 0); 1708 } 1709 1710 #[test] 1711 fn test_empty_rule() { 1712 let ss = Parser::parse("p { }"); 1713 let rule = match &ss.rules[0] { 1714 Rule::Style(r) => r, 1715 _ => panic!("expected style rule"), 1716 }; 1717 assert_eq!(rule.declarations.len(), 0); 1718 } 1719 1720 #[test] 1721 fn test_multiple_rules() { 1722 let ss = Parser::parse("h1 { color: blue; } p { color: red; }"); 1723 assert_eq!(ss.rules.len(), 2); 1724 } 1725 1726 // -- @-rule tests ------------------------------------------------------- 1727 1728 #[test] 1729 fn test_import_rule_string() { 1730 let ss = Parser::parse("@import \"style.css\";"); 1731 assert_eq!(ss.rules.len(), 1); 1732 match &ss.rules[0] { 1733 Rule::Import(r) => assert_eq!(r.url, "style.css"), 1734 _ => panic!("expected import rule"), 1735 } 1736 } 1737 1738 #[test] 1739 fn test_import_rule_url() { 1740 let ss = Parser::parse("@import url(style.css);"); 1741 assert_eq!(ss.rules.len(), 1); 1742 match &ss.rules[0] { 1743 Rule::Import(r) => assert_eq!(r.url, "style.css"), 1744 _ => panic!("expected import rule"), 1745 } 1746 } 1747 1748 #[test] 1749 fn test_media_rule() { 1750 use crate::media::{MediaType, Modifier}; 1751 let ss = Parser::parse("@media screen { p { color: red; } }"); 1752 assert_eq!(ss.rules.len(), 1); 1753 match &ss.rules[0] { 1754 Rule::Media(m) => { 1755 assert_eq!(m.queries.queries.len(), 1); 1756 assert_eq!(m.queries.queries[0].media_type, MediaType::Screen); 1757 assert_eq!(m.queries.queries[0].modifier, Modifier::None); 1758 assert_eq!(m.rules.len(), 1); 1759 } 1760 _ => panic!("expected media rule"), 1761 } 1762 } 1763 1764 #[test] 1765 fn test_media_rule_complex_query() { 1766 use crate::media::{Comparison, MediaFeature, MediaType}; 1767 let ss = Parser::parse("@media screen and (max-width: 600px) { p { font-size: 14px; } }"); 1768 match &ss.rules[0] { 1769 Rule::Media(m) => { 1770 assert_eq!(m.queries.queries[0].media_type, MediaType::Screen); 1771 assert_eq!(m.queries.queries[0].features.len(), 1); 1772 assert_eq!( 1773 m.queries.queries[0].features[0], 1774 MediaFeature::Width(Comparison::Lte, 600.0) 1775 ); 1776 assert_eq!(m.rules.len(), 1); 1777 } 1778 _ => panic!("expected media rule"), 1779 } 1780 } 1781 1782 #[test] 1783 fn test_unknown_at_rule_skipped() { 1784 let ss = Parser::parse("@charset \"UTF-8\"; p { color: red; }"); 1785 assert_eq!(ss.rules.len(), 1); 1786 match &ss.rules[0] { 1787 Rule::Style(r) => assert_eq!(r.declarations[0].property, "color"), 1788 _ => panic!("expected style rule"), 1789 } 1790 } 1791 1792 // -- Integration tests -------------------------------------------------- 1793 1794 #[test] 1795 fn test_real_css() { 1796 let css = r#" 1797 body { 1798 margin: 0; 1799 font-family: sans-serif; 1800 background-color: #fff; 1801 } 1802 h1 { 1803 color: #333; 1804 font-size: 24px; 1805 } 1806 .container { 1807 max-width: 960px; 1808 margin: 0 auto; 1809 } 1810 a:hover { 1811 color: blue; 1812 text-decoration: underline; 1813 } 1814 "#; 1815 let ss = Parser::parse(css); 1816 assert_eq!(ss.rules.len(), 4); 1817 } 1818 1819 #[test] 1820 fn test_declaration_no_trailing_semicolon() { 1821 let ss = Parser::parse("p { color: red }"); 1822 let rule = match &ss.rules[0] { 1823 Rule::Style(r) => r, 1824 _ => panic!("expected style rule"), 1825 }; 1826 assert_eq!(rule.declarations.len(), 1); 1827 assert_eq!(rule.declarations[0].property, "color"); 1828 } 1829 1830 #[test] 1831 fn test_multi_value_declaration() { 1832 let ss = Parser::parse("p { margin: 10px 20px 30px 40px; }"); 1833 let rule = match &ss.rules[0] { 1834 Rule::Style(r) => r, 1835 _ => panic!("expected style rule"), 1836 }; 1837 // 10px WS 20px WS 30px WS 40px 1838 assert_eq!(rule.declarations[0].value.len(), 7); 1839 } 1840 1841 #[test] 1842 fn test_cdo_cdc_ignored() { 1843 let ss = Parser::parse("<!-- p { color: red; } -->"); 1844 assert_eq!(ss.rules.len(), 1); 1845 } 1846 1847 // -- @keyframes tests -- 1848 1849 #[test] 1850 fn test_keyframes_from_to() { 1851 let ss = Parser::parse("@keyframes slidein { from { opacity: 0; } to { opacity: 1; } }"); 1852 assert_eq!(ss.rules.len(), 1); 1853 let kf = match &ss.rules[0] { 1854 Rule::Keyframes(k) => k, 1855 _ => panic!("expected keyframes rule"), 1856 }; 1857 assert_eq!(kf.name, "slidein"); 1858 assert_eq!(kf.keyframes.len(), 2); 1859 assert_eq!(kf.keyframes[0].selectors, vec![KeyframeSelector::From]); 1860 assert_eq!(kf.keyframes[1].selectors, vec![KeyframeSelector::To]); 1861 assert_eq!(kf.keyframes[0].declarations.len(), 1); 1862 assert_eq!(kf.keyframes[0].declarations[0].property, "opacity"); 1863 } 1864 1865 #[test] 1866 fn test_keyframes_percentages() { 1867 let ss = Parser::parse( 1868 "@keyframes fade { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0.5; } }", 1869 ); 1870 let kf = match &ss.rules[0] { 1871 Rule::Keyframes(k) => k, 1872 _ => panic!("expected keyframes rule"), 1873 }; 1874 assert_eq!(kf.name, "fade"); 1875 assert_eq!(kf.keyframes.len(), 3); 1876 assert_eq!( 1877 kf.keyframes[0].selectors, 1878 vec![KeyframeSelector::Percentage(0.0)] 1879 ); 1880 assert_eq!( 1881 kf.keyframes[1].selectors, 1882 vec![KeyframeSelector::Percentage(50.0)] 1883 ); 1884 assert_eq!( 1885 kf.keyframes[2].selectors, 1886 vec![KeyframeSelector::Percentage(100.0)] 1887 ); 1888 } 1889 1890 #[test] 1891 fn test_keyframes_multiple_selectors() { 1892 let ss = 1893 Parser::parse("@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }"); 1894 let kf = match &ss.rules[0] { 1895 Rule::Keyframes(k) => k, 1896 _ => panic!("expected keyframes rule"), 1897 }; 1898 assert_eq!(kf.keyframes.len(), 2); 1899 assert_eq!( 1900 kf.keyframes[0].selectors, 1901 vec![ 1902 KeyframeSelector::Percentage(0.0), 1903 KeyframeSelector::Percentage(100.0), 1904 ] 1905 ); 1906 } 1907 1908 #[test] 1909 fn test_keyframes_string_name() { 1910 let ss = Parser::parse( 1911 "@keyframes \"my animation\" { from { color: red; } to { color: blue; } }", 1912 ); 1913 let kf = match &ss.rules[0] { 1914 Rule::Keyframes(k) => k, 1915 _ => panic!("expected keyframes rule"), 1916 }; 1917 assert_eq!(kf.name, "my animation"); 1918 } 1919 1920 #[test] 1921 fn test_keyframes_webkit_prefix() { 1922 let ss = 1923 Parser::parse("@-webkit-keyframes spin { from { opacity: 0; } to { opacity: 1; } }"); 1924 assert_eq!(ss.rules.len(), 1); 1925 let kf = match &ss.rules[0] { 1926 Rule::Keyframes(k) => k, 1927 _ => panic!("expected keyframes rule"), 1928 }; 1929 assert_eq!(kf.name, "spin"); 1930 } 1931 1932 #[test] 1933 fn test_keyframes_multiple_declarations() { 1934 let ss = Parser::parse( 1935 "@keyframes move { from { left: 0px; top: 0px; } to { left: 100px; top: 50px; } }", 1936 ); 1937 let kf = match &ss.rules[0] { 1938 Rule::Keyframes(k) => k, 1939 _ => panic!("expected keyframes rule"), 1940 }; 1941 assert_eq!(kf.keyframes[0].declarations.len(), 2); 1942 assert_eq!(kf.keyframes[1].declarations.len(), 2); 1943 } 1944 1945 #[test] 1946 fn test_keyframes_with_style_rules() { 1947 let ss = Parser::parse( 1948 r#" 1949 .box { color: red; } 1950 @keyframes fade { from { opacity: 0; } to { opacity: 1; } } 1951 p { font-size: 16px; } 1952 "#, 1953 ); 1954 assert_eq!(ss.rules.len(), 3); 1955 assert!(matches!(ss.rules[0], Rule::Style(_))); 1956 assert!(matches!(ss.rules[1], Rule::Keyframes(_))); 1957 assert!(matches!(ss.rules[2], Rule::Style(_))); 1958 } 1959 1960 // -- @font-face ----------------------------------------------------------- 1961 1962 #[test] 1963 fn font_face_basic() { 1964 let ss = Parser::parse( 1965 r#"@font-face { 1966 font-family: "MyFont"; 1967 src: url("myfont.ttf"); 1968 }"#, 1969 ); 1970 assert_eq!(ss.rules.len(), 1); 1971 let ff = match &ss.rules[0] { 1972 Rule::FontFace(ff) => ff, 1973 _ => panic!("expected FontFace rule"), 1974 }; 1975 assert_eq!(ff.family, "MyFont"); 1976 assert_eq!(ff.sources.len(), 1); 1977 assert_eq!(ff.sources[0].url, "myfont.ttf"); 1978 assert!(ff.sources[0].format.is_none()); 1979 assert_eq!(ff.weight, 400); 1980 assert_eq!(ff.style, FontFaceStyle::Normal); 1981 assert_eq!(ff.display, FontDisplay::Auto); 1982 } 1983 1984 #[test] 1985 fn font_face_all_descriptors() { 1986 let ss = Parser::parse( 1987 r#"@font-face { 1988 font-family: "WebFont"; 1989 src: url("webfont.woff2") format("woff2"); 1990 font-weight: 700; 1991 font-style: italic; 1992 font-display: swap; 1993 }"#, 1994 ); 1995 assert_eq!(ss.rules.len(), 1); 1996 let ff = match &ss.rules[0] { 1997 Rule::FontFace(ff) => ff, 1998 _ => panic!("expected FontFace rule"), 1999 }; 2000 assert_eq!(ff.family, "WebFont"); 2001 assert_eq!(ff.sources.len(), 1); 2002 assert_eq!(ff.sources[0].url, "webfont.woff2"); 2003 assert_eq!(ff.sources[0].format.as_deref(), Some("woff2")); 2004 assert_eq!(ff.weight, 700); 2005 assert_eq!(ff.style, FontFaceStyle::Italic); 2006 assert_eq!(ff.display, FontDisplay::Swap); 2007 } 2008 2009 #[test] 2010 fn font_face_bold_keyword() { 2011 let ss = Parser::parse( 2012 r#"@font-face { 2013 font-family: "BoldFont"; 2014 src: url("bold.ttf"); 2015 font-weight: bold; 2016 }"#, 2017 ); 2018 let ff = match &ss.rules[0] { 2019 Rule::FontFace(ff) => ff, 2020 _ => panic!("expected FontFace rule"), 2021 }; 2022 assert_eq!(ff.weight, 700); 2023 } 2024 2025 #[test] 2026 fn font_face_normal_weight_keyword() { 2027 let ss = Parser::parse( 2028 r#"@font-face { 2029 font-family: "NormalFont"; 2030 src: url("normal.ttf"); 2031 font-weight: normal; 2032 }"#, 2033 ); 2034 let ff = match &ss.rules[0] { 2035 Rule::FontFace(ff) => ff, 2036 _ => panic!("expected FontFace rule"), 2037 }; 2038 assert_eq!(ff.weight, 400); 2039 } 2040 2041 #[test] 2042 fn font_face_oblique_style() { 2043 let ss = Parser::parse( 2044 r#"@font-face { 2045 font-family: "ObliqueFont"; 2046 src: url("oblique.ttf"); 2047 font-style: oblique; 2048 }"#, 2049 ); 2050 let ff = match &ss.rules[0] { 2051 Rule::FontFace(ff) => ff, 2052 _ => panic!("expected FontFace rule"), 2053 }; 2054 assert_eq!(ff.style, FontFaceStyle::Oblique); 2055 } 2056 2057 #[test] 2058 fn font_face_multiple_sources() { 2059 let ss = Parser::parse( 2060 r#"@font-face { 2061 font-family: "MultiFmt"; 2062 src: url("font.woff2") format("woff2"), url("font.ttf") format("truetype"); 2063 }"#, 2064 ); 2065 let ff = match &ss.rules[0] { 2066 Rule::FontFace(ff) => ff, 2067 _ => panic!("expected FontFace rule"), 2068 }; 2069 assert_eq!(ff.sources.len(), 2); 2070 assert_eq!(ff.sources[0].url, "font.woff2"); 2071 assert_eq!(ff.sources[0].format.as_deref(), Some("woff2")); 2072 assert_eq!(ff.sources[1].url, "font.ttf"); 2073 assert_eq!(ff.sources[1].format.as_deref(), Some("truetype")); 2074 } 2075 2076 #[test] 2077 fn font_face_display_values() { 2078 for (keyword, expected) in [ 2079 ("auto", FontDisplay::Auto), 2080 ("block", FontDisplay::Block), 2081 ("swap", FontDisplay::Swap), 2082 ("fallback", FontDisplay::Fallback), 2083 ("optional", FontDisplay::Optional), 2084 ] { 2085 let css = format!( 2086 r#"@font-face {{ 2087 font-family: "F"; 2088 src: url("f.ttf"); 2089 font-display: {}; 2090 }}"#, 2091 keyword 2092 ); 2093 let ss = Parser::parse(&css); 2094 let ff = match &ss.rules[0] { 2095 Rule::FontFace(ff) => ff, 2096 _ => panic!("expected FontFace rule"), 2097 }; 2098 assert_eq!(ff.display, expected, "font-display: {}", keyword); 2099 } 2100 } 2101 2102 #[test] 2103 fn font_face_missing_family_skipped() { 2104 let ss = Parser::parse( 2105 r#"@font-face { 2106 src: url("nofamily.ttf"); 2107 }"#, 2108 ); 2109 assert!( 2110 ss.rules.is_empty(), 2111 "should skip @font-face without font-family" 2112 ); 2113 } 2114 2115 #[test] 2116 fn font_face_missing_src_skipped() { 2117 let ss = Parser::parse( 2118 r#"@font-face { 2119 font-family: "NoSrc"; 2120 }"#, 2121 ); 2122 assert!(ss.rules.is_empty(), "should skip @font-face without src"); 2123 } 2124 2125 #[test] 2126 fn font_face_unquoted_family() { 2127 let ss = Parser::parse( 2128 r#"@font-face { 2129 font-family: CustomFont; 2130 src: url("custom.ttf"); 2131 }"#, 2132 ); 2133 assert_eq!(ss.rules.len(), 1); 2134 let ff = match &ss.rules[0] { 2135 Rule::FontFace(ff) => ff, 2136 _ => panic!("expected FontFace rule"), 2137 }; 2138 assert_eq!(ff.family, "CustomFont"); 2139 } 2140 2141 #[test] 2142 fn font_face_among_other_rules() { 2143 let ss = Parser::parse( 2144 r#" 2145 body { color: black; } 2146 @font-face { 2147 font-family: "TestFont"; 2148 src: url("test.ttf"); 2149 } 2150 p { margin: 0; } 2151 "#, 2152 ); 2153 assert_eq!(ss.rules.len(), 3); 2154 assert!(matches!(ss.rules[0], Rule::Style(_))); 2155 assert!(matches!(ss.rules[1], Rule::FontFace(_))); 2156 assert!(matches!(ss.rules[2], Rule::Style(_))); 2157 } 2158 2159 #[test] 2160 fn font_face_numeric_weight() { 2161 let ss = Parser::parse( 2162 r#"@font-face { 2163 font-family: "W300"; 2164 src: url("w300.ttf"); 2165 font-weight: 300; 2166 }"#, 2167 ); 2168 let ff = match &ss.rules[0] { 2169 Rule::FontFace(ff) => ff, 2170 _ => panic!("expected FontFace rule"), 2171 }; 2172 assert_eq!(ff.weight, 300); 2173 } 2174}