code wiki / (root) / nx_css_selector_match.nx

nx_css_selector_match.nx source

↩ module page · 165 lines · 6027 B

1// nx_css_selector_match.nx -- match a parsed CssRule selector against 2// an element's (tag, id, class-list). Phase 2 third primitive after 3// nx_css_tokenize and nx_css_parse; the core of the eventual 4// nx_css_apply layer that walks a DOM tree and cascades styles. 5// 6// Subset matched: 7// TAG selector -- exact case-sensitive byte-equal vs element tag 8// CLASS selector -- one of the whitespace-separated tokens in the 9// element's class attribute byte-equal 10// ID selector -- byte-equal vs the element's id attribute 11// 12// What it does NOT handle yet (named Phase 2b improvements): 13// - case-insensitive tag matching for HTML mode (HTML5 lowercases 14// tags; until nx_html_tokenizer normalizes, callers can pre-lowercase) 15// - compound selectors (h1.foo) -- needs Phase 2b 16// - descendant chains (header h1) -- needs DOM-tree context 17// - sibling/child combinators (+ ~ >) 18// - attribute selectors [href^="..."] 19// - pseudo-classes (:hover, :nth-child) 20// - namespace selectors 21// 22// Per cardinal feedback-honest-perf-verdict-no-aspirational-claims: 23// gap list is EXACT and tested. 24// 25// genealogy_id: w3c_css_selectors_module_level_3 + 26// substrate_browser_phase_2_selector_match 27// lineage_id: nishi_browser_css_selector_match_v1 28// 29// nx_safety_envelope: 30// intended_use: "CSS selector match -- browser front-end style apply" 31// sil_target: SIL1 32// evidence: [W3C_CSS_Selectors_canonical_basis, 33// sealed_selector_kind_enum, 34// bounded_class_list_walk, 35// pure_function_no_allocation] 36// verdict: NOT_YET_EVALUATED 37 38import "nx_syscalls.nx" 39import "nx_css_tokenize.nx" 40import "nx_css_parse.nx" 41 42// Element view -- caller-supplied. Offsets/lengths are into a single 43// document buffer shared with the parsed CssRule (so byte equality is 44// well-defined). Class attribute is the FULL attribute value 45// (potentially multiple whitespace-separated classes; matcher walks 46// the tokens internally). 47struct CssElement { 48 src: *u8, // document bytes (shared with parsed CssRule) 49 tag_off: i64, 50 tag_len: i64, 51 id_off: i64, // 0/0 if no id attribute 52 id_len: i64, 53 class_off: i64, // 0/0 if no class attribute 54 class_len: i64 55} 56 57const NX_CSS_ELEMENT_BYTES: i64 = 56 58 59// ---- helpers ---- 60 61// Byte-equal compare two regions of the same source buffer. Returns 1 if 62// equal, 0 otherwise. Two zero-length regions compare equal. 63func _css_bytes_equal(src: *u8, a_off: i64, a_len: i64, 64 b_off: i64, b_len: i64) -> i64 { 65 if a_len != b_len { return 0 } 66 var i: i64 = 0 67 while i < a_len { 68 let ca: i64 = (src[a_off + i] as i64) & 255 69 let cb: i64 = (src[b_off + i] as i64) & 255 70 if ca != cb { return 0 } 71 i = i + 1 72 } 73 return 1 74} 75 76func _css_is_class_separator(c: i64) -> i64 { 77 if c == 32 { return 1 } // ' ' 78 if c == 9 { return 1 } // \t 79 if c == 10 { return 1 } // \n 80 if c == 13 { return 1 } // \r 81 if c == 12 { return 1 } // \f 82 return 0 83} 84 85// Check whether the element's class attribute contains a token that 86// byte-equals (sel_off, sel_len) in the source buffer. Whitespace- 87// separated tokens per HTML5 attribute parsing. 88func _css_class_list_contains(src: *u8, class_off: i64, class_len: i64, 89 sel_off: i64, sel_len: i64) -> i64 { 90 if sel_len == 0 { return 0 } 91 if class_len == 0 { return 0 } 92 var i: i64 = 0 93 while i < class_len { 94 // Skip leading separators. 95 var c: i64 = (src[class_off + i] as i64) & 255 96 var keep_skip: i64 = 1 97 while keep_skip == 1 { 98 if i >= class_len { keep_skip = 0 } 99 else { 100 c = (src[class_off + i] as i64) & 255 101 if _css_is_class_separator(c) == 1 { i = i + 1 } 102 else { keep_skip = 0 } 103 } 104 } 105 if i >= class_len { return 0 } 106 // Mark token start. 107 let tok_start: i64 = class_off + i 108 var keep_tok: i64 = 1 109 while keep_tok == 1 { 110 if i >= class_len { keep_tok = 0 } 111 else { 112 c = (src[class_off + i] as i64) & 255 113 if _css_is_class_separator(c) == 1 { keep_tok = 0 } 114 else { i = i + 1 } 115 } 116 } 117 let tok_len: i64 = (class_off + i) - tok_start 118 if _css_bytes_equal(src, tok_start, tok_len, sel_off, sel_len) == 1 { 119 return 1 120 } 121 } 122 return 0 123} 124 125// ---- public API ---- 126 127// Initialize a CssElement view in-place. 128func nx_css_element_init(el: *CssElement, src: *u8, 129 tag_off: i64, tag_len: i64, 130 id_off: i64, id_len: i64, 131 class_off: i64, class_len: i64) -> i64 { 132 el.src = src 133 el.tag_off = tag_off 134 el.tag_len = tag_len 135 el.id_off = id_off 136 el.id_len = id_len 137 el.class_off = class_off 138 el.class_len = class_len 139 return 0 140} 141 142// Return 1 if the given parsed CssRule's simple selector matches the 143// element, 0 otherwise. The CssRule and CssElement MUST share the 144// same source buffer (or have byte-identical regions at the named 145// offsets); this primitive does not own or interpret either buffer. 146func nx_css_selector_match(rule: *CssRule, el: *CssElement) -> i64 { 147 let kind: i64 = rule.sel_kind 148 149 if kind == NX_CSS_SEL_TAG { 150 return _css_bytes_equal(el.src, el.tag_off, el.tag_len, 151 rule.sel_off, rule.sel_len) 152 } 153 154 if kind == NX_CSS_SEL_CLASS { 155 return _css_class_list_contains(el.src, el.class_off, el.class_len, 156 rule.sel_off, rule.sel_len) 157 } 158 159 if kind == NX_CSS_SEL_ID { 160 return _css_bytes_equal(el.src, el.id_off, el.id_len, 161 rule.sel_off, rule.sel_len) 162 } 163 164 return 0 165}