code wiki / (root) / nx_domtree.nx

nx_domtree.nx source

↩ module page · 840 lines · 35765 B

1// nx_domtree.nx -- a MATERIALIZED, MUTABLE DOM tree for the sovereign browser. 2// 3// The JS engine's existing DOM is a READ-ONLY byte snapshot (nx_js_eval: genv[2]=html ptr, 4// queries re-scan the bytes). To RENDER JS-built content, page scripts must MUTATE the DOM and 5// have the result laid out. This organ is that missing piece: parse HTML -> a node arena (REUSING 6// nx_html_tokenizer, no duplicate parser), MUTATE it (createElement/appendChild/textContent=/ 7// setAttribute), and SERIALIZE it back to HTML for nx_layout. Rung A of general JS rendering. 8// module: nx_domtree capability: mutable-dom 9// Sovereign: imports only nx_syscalls + the gated nx_html_tokenizer. No gcc/V8/libxml. ORIGINAL. 10import "nx_syscalls.nx" 11import "nx_html_tokenizer.nx" 12const DT_MAGIC_4096: i64 = 4096 13const DT_MAGIC_65536: i64 = 65536 14 15const DT_ELEM: i64 = 1 16const DT_TEXT: i64 = 2 17const DT_DOC: i64 = 3 18const DT_NODE_BYTES: i64 = 96 // 12 i64 fields 19const DT_ATTR_BYTES: i64 = 16 // 2 i64 fields 20const DT_ATTR_CAP: i64 = 32 // max attrs per element (rung-A bound) 21const DT_MAX_NODES: i64 = 131072 // node arena capacity (mmap lazy zero-fill) 22 23struct DomNode { 24 kind: i64, // DT_ELEM / DT_TEXT / DT_DOC 25 parent: i64, // node index, or -1 26 first_child: i64, // node index, or -1 27 last_child: i64, // node index, or -1 (O(1) append) 28 next_sib: i64, // node index, or -1 29 tag: i64, // *u8 owned NUL-term lowercased tag name (DT_ELEM), else 0 30 text: i64, // *u8 owned text bytes (DT_TEXT), else 0 31 text_len: i64, 32 attrs: i64, // *DtAttr owned array, or 0 33 nattrs: i64, 34 voidf: i64, // 1 = void element (img/br/...) -> no close tag, no children 35 jswrap: i64 // cached JS element-wrapper obj ptr (0=none) -> STABLE element identity (dataPriv/events/===) 36} 37struct DtAttr { 38 name: i64, // *u8 owned NUL-term (lowercased) 39 val: i64 // *u8 owned NUL-term 40} 41struct DomTree { 42 nodes: i64, // *u8 node arena base 43 n: i64, // node count 44 cap: i64, 45 root: i64 // root node index (DT_DOC) 46} 47 48// ---- small helpers ---- 49func dt_clen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 50 51func dt_ceq(a: *u8, b: *u8) -> i64 { 52 var i: i64 = 0 53 while 1 == 1 { 54 let ca: i64 = a[i] & 0xff 55 let cb: i64 = b[i] & 0xff 56 if ca != cb { return 0 } 57 if ca == 0 { return 1 } 58 i = i + 1 59 } 60 return 0 61} 62// owned NUL-terminated copy of src[off, off+len) 63func dt_dup(src: *u8, off: i64, len: i64) -> i64 { 64 var n: i64 = len 65 if n < 0 { n = 0 } 66 let d: *u8 = sys_mmap(n + 1) 67 var i: i64 = 0 68 while i < n { d[i] = src[off + i]; i = i + 1 } 69 d[n] = 0 as u8 70 return d as i64 71} 72// owned NUL-terminated LOWERCASED copy of src[off, off+len) 73func dt_dup_lower(src: *u8, off: i64, len: i64) -> i64 { 74 var n: i64 = len 75 if n < 0 { n = 0 } 76 let d: *u8 = sys_mmap(n + 1) 77 var i: i64 = 0 78 while i < n { 79 var b: i64 = src[off + i] & 0xff 80 if b >= 65 { if b <= 90 { b = b + 32 } } 81 d[i] = b as u8 82 i = i + 1 83 } 84 d[n] = 0 as u8 85 return d as i64 86} 87func dt_dup_c(s: *u8) -> i64 { return dt_dup(s, 0, dt_clen(s)) } 88func dt_dup_lower_c(s: *u8) -> i64 { return dt_dup_lower(s, 0, dt_clen(s)) } 89 90// ---- node arena ---- 91func dt_node(t: *DomTree, idx: i64) -> *DomNode { 92 return ((t.nodes) + idx * DT_NODE_BYTES) as *DomNode 93} 94func dt_alloc(t: *DomTree, kind: i64) -> i64 { 95 let idx: i64 = t.n 96 t.n = t.n + 1 97 let nd: *DomNode = dt_node(t, idx) 98 nd.kind = kind 99 nd.parent = 0 - 1 100 nd.first_child = 0 - 1 101 nd.last_child = 0 - 1 102 nd.next_sib = 0 - 1 103 nd.tag = 0 104 nd.text = 0 105 nd.text_len = 0 106 nd.attrs = 0 107 nd.nattrs = 0 108 nd.voidf = 0 109 nd.jswrap = 0 110 return idx 111} 112func dt_new() -> *DomTree { 113 let t: *DomTree = sys_mmap(32) as *DomTree 114 t.nodes = (sys_mmap(DT_MAX_NODES * DT_NODE_BYTES)) as i64 115 t.n = 0 116 t.cap = DT_MAX_NODES 117 t.root = dt_alloc(t, DT_DOC) 118 return t 119} 120 121// is this lowercased tag a void element? 122func dt_is_void(tag: *u8) -> i64 { 123 if dt_ceq(tag, "img\x00" as *u8) == 1 { return 1 } 124 if dt_ceq(tag, "br\x00" as *u8) == 1 { return 1 } 125 if dt_ceq(tag, "hr\x00" as *u8) == 1 { return 1 } 126 if dt_ceq(tag, "input\x00" as *u8) == 1 { return 1 } 127 if dt_ceq(tag, "meta\x00" as *u8) == 1 { return 1 } 128 if dt_ceq(tag, "link\x00" as *u8) == 1 { return 1 } 129 if dt_ceq(tag, "area\x00" as *u8) == 1 { return 1 } 130 if dt_ceq(tag, "base\x00" as *u8) == 1 { return 1 } 131 if dt_ceq(tag, "col\x00" as *u8) == 1 { return 1 } 132 if dt_ceq(tag, "embed\x00" as *u8) == 1 { return 1 } 133 if dt_ceq(tag, "param\x00" as *u8) == 1 { return 1 } 134 if dt_ceq(tag, "source\x00" as *u8) == 1 { return 1 } 135 if dt_ceq(tag, "track\x00" as *u8) == 1 { return 1 } 136 if dt_ceq(tag, "wbr\x00" as *u8) == 1 { return 1 } 137 return 0 138} 139 140func dt_create_element_span(t: *DomTree, src: *u8, off: i64, len: i64) -> i64 { 141 let idx: i64 = dt_alloc(t, DT_ELEM) 142 let nd: *DomNode = dt_node(t, idx) 143 nd.tag = dt_dup_lower(src, off, len) 144 nd.voidf = dt_is_void(nd.tag as *u8) 145 return idx 146} 147func dt_create_element(t: *DomTree, tagc: *u8) -> i64 { 148 return dt_create_element_span(t, tagc, 0, dt_clen(tagc)) 149} 150func dt_create_text(t: *DomTree, src: *u8, off: i64, len: i64) -> i64 { 151 let idx: i64 = dt_alloc(t, DT_TEXT) 152 let nd: *DomNode = dt_node(t, idx) 153 nd.text = dt_dup(src, off, len) 154 var n: i64 = len 155 if n < 0 { n = 0 } 156 nd.text_len = n 157 return idx 158} 159 160func dt_append_child(t: *DomTree, parent: i64, child: i64) -> i64 { 161 let p: *DomNode = dt_node(t, parent) 162 let c: *DomNode = dt_node(t, child) 163 c.parent = parent 164 c.next_sib = 0 - 1 165 if p.first_child == (0 - 1) { 166 p.first_child = child 167 p.last_child = child 168 } else { 169 let last: *DomNode = dt_node(t, p.last_child) 170 last.next_sib = child 171 p.last_child = child 172 } 173 return 0 174} 175// UNLINK child from parent's sibling chain (real Node.removeChild / jQuery .remove()/.detach()/.empty()). 176// Repairs first_child/last_child/prev.next_sib and orphans the child (parent=-1). No-op if not a child. 177func dt_remove_child(t: *DomTree, parent: i64, child: i64) -> i64 { 178 let p: *DomNode = dt_node(t, parent) 179 if p.first_child == child { 180 let c: *DomNode = dt_node(t, child) 181 p.first_child = c.next_sib 182 if p.last_child == child { p.last_child = c.next_sib } // was the only child -> chain now empty (-1) 183 c.parent = 0 - 1 184 c.next_sib = 0 - 1 185 return 0 186 } 187 var prev: i64 = p.first_child 188 while prev != (0 - 1) { 189 let pn: *DomNode = dt_node(t, prev) 190 if pn.next_sib == child { 191 let c: *DomNode = dt_node(t, child) 192 pn.next_sib = c.next_sib 193 if p.last_child == child { p.last_child = prev } 194 c.parent = 0 - 1 195 c.next_sib = 0 - 1 196 return 0 197 } 198 prev = pn.next_sib 199 } 200 return 0 201} 202// deep-clone the subtree rooted at `src` -> new node index (children recursively cloned + re-parented). The 203// node arena is fixed (pre-mmap'd, no realloc), so DomNode ptrs stay valid across dt_alloc during recursion. 204// Used by Element.cloneNode(true) -- jQuery's support-detects clone a populated node + inspect the copy. 205static dt_clone_depth: i64 206func dt_clone_subtree(t: *DomTree, src: i64) -> i64 { 207 if src < 0 { return dt_create_text(t, "\x00" as *u8, 0, 0) } 208 if src >= t.n { return dt_create_text(t, "\x00" as *u8, 0, 0) } // out-of-range index guard 209 if dt_clone_depth > DT_MAGIC_4096 { return dt_create_text(t, "\x00" as *u8, 0, 0) } // cycle/too-deep -> stop (never crash) 210 dt_clone_depth = dt_clone_depth + 1 211 let s: *DomNode = dt_node(t, src) 212 var idx: i64 = 0 - 1 213 if s.kind == DT_TEXT { 214 idx = dt_create_text(t, s.text as *u8, 0, s.text_len) 215 } else { 216 idx = dt_alloc(t, DT_ELEM) 217 let nd: *DomNode = dt_node(t, idx) 218 if s.tag != 0 { nd.tag = dt_dup(s.tag as *u8, 0, dt_clen(s.tag as *u8)) } 219 nd.voidf = s.voidf 220 var ai: i64 = 0 221 while ai < s.nattrs { 222 let ap: *DtAttr = dt_attr_at(s.attrs as *DtAttr, ai) 223 if (ap.name as i64) != 0 { dt_set_attr(t, idx, ap.name as *u8, ap.val as *u8) } 224 ai = ai + 1 225 } 226 var ch: i64 = s.first_child 227 var ccount: i64 = 0 228 while ch != (0 - 1) { 229 if ch < 0 { ch = 0 - 1 } else { if ch >= t.n { ch = 0 - 1 } else { if ccount > DT_MAGIC_65536 { ch = 0 - 1 } else { 230 let cc: i64 = dt_clone_subtree(t, ch) 231 dt_append_child(t, idx, cc) 232 ccount = ccount + 1 233 let cn: *DomNode = dt_node(t, ch) 234 ch = cn.next_sib 235 } } } 236 } 237 } 238 dt_clone_depth = dt_clone_depth - 1 239 return idx 240} 241 242// ---- attributes ---- 243func dt_attr_at(base: *DtAttr, i: i64) -> *DtAttr { 244 return ((base as i64) + i * DT_ATTR_BYTES) as *DtAttr 245} 246// set/update attribute (name + value given as raw spans). name is lowercased + owned. 247func dt_set_attr_span(t: *DomTree, node: i64, nsrc: *u8, noff: i64, nlen: i64, vsrc: *u8, voff: i64, vlen: i64) -> i64 { 248 let nd: *DomNode = dt_node(t, node) 249 if nd.attrs == 0 { 250 nd.attrs = (sys_mmap(DT_ATTR_CAP * DT_ATTR_BYTES)) as i64 251 nd.nattrs = 0 252 } 253 let lname: i64 = dt_dup_lower(nsrc, noff, nlen) 254 let base: *DtAttr = nd.attrs as *DtAttr 255 var i: i64 = 0 256 while i < nd.nattrs { 257 let a: *DtAttr = dt_attr_at(base, i) 258 if dt_ceq(a.name as *u8, lname as *u8) == 1 { 259 a.val = dt_dup(vsrc, voff, vlen) 260 return 0 261 } 262 i = i + 1 263 } 264 if nd.nattrs >= DT_ATTR_CAP { return 0 - 1 } 265 let a2: *DtAttr = dt_attr_at(base, nd.nattrs) 266 a2.name = lname 267 a2.val = dt_dup(vsrc, voff, vlen) 268 nd.nattrs = nd.nattrs + 1 269 return 0 270} 271func dt_set_attr(t: *DomTree, node: i64, namec: *u8, valc: *u8) -> i64 { 272 return dt_set_attr_span(t, node, namec, 0, dt_clen(namec), valc, 0, dt_clen(valc)) 273} 274// returns owned value cstr, or 0 if absent 275func dt_get_attr(t: *DomTree, node: i64, namec: *u8) -> *u8 { 276 let nd: *DomNode = dt_node(t, node) 277 if nd.attrs == 0 { return 0 as *u8 } 278 let lname: i64 = dt_dup_lower_c(namec) 279 let base: *DtAttr = nd.attrs as *DtAttr 280 var i: i64 = 0 281 while i < nd.nattrs { 282 let a: *DtAttr = dt_attr_at(base, i) 283 if dt_ceq(a.name as *u8, lname as *u8) == 1 { return a.val as *u8 } 284 i = i + 1 285 } 286 return 0 as *u8 287} 288 289// textContent = "..." : drop all children, install one text node 290func dt_set_text_content(t: *DomTree, node: i64, src: *u8, off: i64, len: i64) -> i64 { 291 let nd: *DomNode = dt_node(t, node) 292 nd.first_child = 0 - 1 293 nd.last_child = 0 - 1 294 // textContent="" removes all children and leaves NONE (real DOM). Appending an empty text node 295 // here left a stray #text -- jQuery's buildFragment does `fragment.textContent=""` to drop its temp 296 // <div>, so the stray node made parseHTML return an extra leading text node. Empty -> clear only. 297 if len <= 0 { return 0 } 298 let tx: i64 = dt_create_text(t, src, off, len) 299 dt_append_child(t, node, tx) 300 return 0 301} 302 303// ---- queries ---- 304func dt_gebi_rec(t: *DomTree, idx: i64, idc: *u8) -> i64 { 305 if idx == (0 - 1) { return 0 - 1 } 306 let nd: *DomNode = dt_node(t, idx) 307 if nd.kind == DT_ELEM { 308 let v: *u8 = dt_get_attr(t, idx, "id\x00" as *u8) 309 if (v as i64) != 0 { if dt_ceq(v, idc) == 1 { return idx } } 310 } 311 var c: i64 = nd.first_child 312 while c != (0 - 1) { 313 let r: i64 = dt_gebi_rec(t, c, idc) 314 if r != (0 - 1) { return r } 315 let cn: *DomNode = dt_node(t, c) 316 c = cn.next_sib 317 } 318 return 0 - 1 319} 320func dt_get_element_by_id(t: *DomTree, idc: *u8) -> i64 { return dt_gebi_rec(t, t.root, idc) } 321 322func dt_fbt_rec(t: *DomTree, idx: i64, tagc: *u8) -> i64 { 323 if idx == (0 - 1) { return 0 - 1 } 324 let nd: *DomNode = dt_node(t, idx) 325 if nd.kind == DT_ELEM { if dt_ceq(nd.tag as *u8, tagc) == 1 { return idx } } 326 var c: i64 = nd.first_child 327 while c != (0 - 1) { 328 let r: i64 = dt_fbt_rec(t, c, tagc) 329 if r != (0 - 1) { return r } 330 let cn: *DomNode = dt_node(t, c) 331 c = cn.next_sib 332 } 333 return 0 - 1 334} 335func dt_first_by_tag(t: *DomTree, tagc: *u8) -> i64 { return dt_fbt_rec(t, t.root, tagc) } 336 337// does node's class attr contain `cls` as a whole space-separated token? 338func dt_has_class(t: *DomTree, node: i64, cls: *u8) -> i64 { 339 let v: *u8 = dt_get_attr(t, node, "class\x00" as *u8) 340 if (v as i64) == 0 { return 0 } 341 let cl: i64 = dt_clen(cls) 342 if cl == 0 { return 0 } 343 var i: i64 = 0 344 while v[i] != (0 as u8) { 345 while v[i] == (32 as u8) { i = i + 1 } 346 let s: i64 = i 347 while v[i] != (0 as u8) { if v[i] == (32 as u8) { break } i = i + 1 } 348 let tl: i64 = i - s 349 if tl == cl { 350 var j: i64 = 0 351 var ok: i64 = 1 352 while j < cl { if (v[s + j] & 0xff) != (cls[j] & 0xff) { ok = 0; j = cl } else { j = j + 1 } } 353 if ok == 1 { return 1 } 354 } 355 } 356 return 0 357} 358// selector match: mode 1='#id' (exact id), 2='.class' (token), 3='tag' (name pre-lowercased by caller). 359func dt_sel_match(t: *DomTree, node: i64, mode: i64, name: *u8) -> i64 { 360 let nd: *DomNode = dt_node(t, node) 361 if nd.kind != DT_ELEM { return 0 } 362 if mode == 1 { let v: *u8 = dt_get_attr(t, node, "id\x00" as *u8); if (v as i64) == 0 { return 0 } return dt_ceq(v, name) } 363 if mode == 2 { return dt_has_class(t, node, name) } 364 if (name[0] & 0xff) == 42 { return 1 } // '*' = ANY element (getElementsByTagName("*") / Sizzle universal) 365 return dt_ceq(nd.tag as *u8, name) 366} 367// ============ CSS COMPLEX-SELECTOR MATCHER (compound + attribute + descendant/child combinators) ============ 368// The unified foundation for querySelectorAll(complexSel) + element.matches(sel): jQuery's Sizzle prefers 369// qsa when the whole selector is qsa-able, and uses element.matches for .is/.filter(sel)/.closest/.not. 370// String helpers on NUL-terminated cstrings (attribute-value operators): 371func dt_cstr_contains(hay: *u8, needle: *u8) -> i64 { 372 let hl: i64 = dt_clen(hay); let nl: i64 = dt_clen(needle) 373 if nl == 0 { return 1 } 374 var i: i64 = 0 375 while i + nl <= hl { 376 var j: i64 = 0; var ok: i64 = 1 377 while j < nl { if (hay[i + j] & 0xff) != (needle[j] & 0xff) { ok = 0; j = nl } else { j = j + 1 } } 378 if ok == 1 { return 1 } 379 i = i + 1 380 } 381 return 0 382} 383func dt_cstr_prefix(hay: *u8, needle: *u8) -> i64 { 384 let hl: i64 = dt_clen(hay); let nl: i64 = dt_clen(needle) 385 if nl > hl { return 0 } 386 var j: i64 = 0 387 while j < nl { if (hay[j] & 0xff) != (needle[j] & 0xff) { return 0 } j = j + 1 } 388 return 1 389} 390func dt_cstr_suffix(hay: *u8, needle: *u8) -> i64 { 391 let hl: i64 = dt_clen(hay); let nl: i64 = dt_clen(needle) 392 if nl > hl { return 0 } 393 var j: i64 = 0 394 while j < nl { if (hay[hl - nl + j] & 0xff) != (needle[j] & 0xff) { return 0 } j = j + 1 } 395 return 1 396} 397// attr op: 1='=' exact, 2='*=' substring, 3='^=' prefix, 4='$=' suffix, 5='~=' whitespace-token 398func dt_attr_op(val: *u8, op: i64, want: *u8) -> i64 { 399 if op == 1 { return dt_ceq(val, want) } 400 if op == 2 { return dt_cstr_contains(val, want) } 401 if op == 3 { return dt_cstr_prefix(val, want) } 402 if op == 4 { return dt_cstr_suffix(val, want) } 403 if op == 5 { // ~= : whitespace-separated token (reuse class-token logic shape) 404 let cl: i64 = dt_clen(want) 405 if cl == 0 { return 0 } 406 var i: i64 = 0 407 while val[i] != (0 as u8) { 408 while val[i] == (32 as u8) { i = i + 1 } 409 let s: i64 = i 410 while val[i] != (0 as u8) { if val[i] == (32 as u8) { break } i = i + 1 } 411 let tl: i64 = i - s 412 if tl == cl { var j: i64 = 0; var ok: i64 = 1; while j < cl { if (val[s + j] & 0xff) != (want[j] & 0xff) { ok = 0; j = cl } else { j = j + 1 } } if ok == 1 { return 1 } } 413 } 414 return 0 415 } 416 return 1 417} 418// match ONE compound (no combinators): tagOrStar? (#id | .class | [attr op val] | :scope)* against node. 419func dt_sel_compound(t: *DomTree, node: i64, sel: *u8, a: i64, b: i64, scope_root: i64) -> i64 { 420 let nd: *DomNode = dt_node(t, node) 421 if nd.kind != DT_ELEM { return 0 } 422 var i: i64 = a 423 while i < b { 424 let c: i64 = sel[i] & 0xff 425 if c == 58 { 426 // ':pseudo' -- supported: :scope (matches the querySelectorAll context element) and 427 // :not(sel) (negation; jQuery's .not()/.is(':not(..)') route through this). Any other pseudo 428 // makes this compound NON-matching (Sizzle handles the rest in its own matcher). 429 i = i + 1 430 let s0: i64 = i 431 while i < b { let cc: i64 = sel[i] & 0xff; if cc == 46 { break } if cc == 35 { break } if cc == 91 { break } if cc == 58 { break } if cc == 40 { break } if cc == 32 { break } i = i + 1 } 432 let pn: *u8 = (dt_dup(sel, s0, i - s0)) as *u8 433 // capture an optional (...) argument range [argS, argE) 434 var argS: i64 = 0 - 1 435 var argE: i64 = 0 - 1 436 if i < b { if (sel[i] & 0xff) == 40 { argS = i + 1; var d2: i64 = 1; i = i + 1; while i < b { let ac: i64 = sel[i] & 0xff; if ac == 40 { d2 = d2 + 1 } if ac == 41 { d2 = d2 - 1 } if d2 == 0 { argE = i; i = i + 1; break } i = i + 1 } } } 437 if dt_ceq(pn, "scope\x00" as *u8) == 1 { if node != scope_root { return 0 } } 438 else { if dt_ceq(pn, "not\x00" as *u8) == 1 { 439 if argS >= 0 { let inner: *u8 = (dt_dup(sel, argS, argE - argS)) as *u8; if dt_sel_matches(t, node, inner, argE - argS, scope_root) == 1 { return 0 } } 440 } else { return 0 } } 441 } 442 else { if c == 42 { i = i + 1 } 443 else { if c == 46 { 444 i = i + 1 445 let s0: i64 = i 446 while i < b { let cc: i64 = sel[i] & 0xff; if cc == 46 { break } if cc == 35 { break } if cc == 91 { break } i = i + 1 } 447 let cls: *u8 = (dt_dup(sel, s0, i - s0)) as *u8 448 if dt_has_class(t, node, cls) == 0 { return 0 } 449 } 450 else { if c == 35 { 451 i = i + 1 452 let s0: i64 = i 453 while i < b { let cc: i64 = sel[i] & 0xff; if cc == 46 { break } if cc == 35 { break } if cc == 91 { break } i = i + 1 } 454 let idv: *u8 = dt_get_attr(t, node, "id\x00" as *u8) 455 if (idv as i64) == 0 { return 0 } 456 let want: *u8 = (dt_dup(sel, s0, i - s0)) as *u8 457 if dt_ceq(idv, want) == 0 { return 0 } 458 } 459 else { if c == 91 { 460 i = i + 1 461 let n0: i64 = i 462 while i < b { let cc: i64 = sel[i] & 0xff; if cc == 93 { break } if cc == 61 { break } if cc == 42 { break } if cc == 94 { break } if cc == 36 { break } if cc == 126 { break } i = i + 1 } 463 let an: *u8 = (dt_dup(sel, n0, i - n0)) as *u8 464 let av: *u8 = dt_get_attr(t, node, an) 465 var op: i64 = 0 466 if i < b { 467 let oc: i64 = sel[i] & 0xff 468 if oc == 61 { op = 1; i = i + 1 } 469 else { if oc == 42 { op = 2; i = i + 1; if i < b { if (sel[i] & 0xff) == 61 { i = i + 1 } } } 470 else { if oc == 94 { op = 3; i = i + 1; if i < b { if (sel[i] & 0xff) == 61 { i = i + 1 } } } 471 else { if oc == 36 { op = 4; i = i + 1; if i < b { if (sel[i] & 0xff) == 61 { i = i + 1 } } } 472 else { if oc == 126 { op = 5; i = i + 1; if i < b { if (sel[i] & 0xff) == 61 { i = i + 1 } } } } } } } 473 } 474 var v0: i64 = i 475 while i < b { if (sel[i] & 0xff) == 93 { break } i = i + 1 } 476 var ve: i64 = i 477 if v0 < ve { let q: i64 = sel[v0] & 0xff; if q == 34 { v0 = v0 + 1; if ve > v0 { if (sel[ve - 1] & 0xff) == q { ve = ve - 1 } } } else { if q == 39 { v0 = v0 + 1; if ve > v0 { if (sel[ve - 1] & 0xff) == q { ve = ve - 1 } } } } } 478 if i < b { i = i + 1 } 479 if op == 0 { if (av as i64) == 0 { return 0 } } 480 else { 481 if (av as i64) == 0 { return 0 } 482 let want: *u8 = (dt_dup(sel, v0, ve - v0)) as *u8 483 if dt_attr_op(av, op, want) == 0 { return 0 } 484 } 485 } 486 else { 487 let s0: i64 = i 488 while i < b { let cc: i64 = sel[i] & 0xff; if cc == 46 { break } if cc == 35 { break } if cc == 91 { break } i = i + 1 } 489 let want: *u8 = (dt_dup_lower(sel, s0, i - s0)) as *u8 490 if dt_ceq(nd.tag as *u8, want) == 0 { return 0 } 491 } } } } } 492 } 493 return 1 494} 495// match a COMPLEX selector sel[a:b) (compounds joined by descendant-space or child-'>') against node, 496// RIGHT-TO-LEFT (the rightmost compound must match node; ancestors/parent must match the left part). 497func dt_sel_complex(t: *DomTree, node: i64, sel: *u8, a0: i64, b0: i64, scope_root: i64) -> i64 { 498 var a: i64 = a0 499 var b: i64 = b0 500 while a < b { if (sel[a] & 0xff) == 32 { a = a + 1 } else { break } } 501 while b > a { if (sel[b - 1] & 0xff) == 32 { b = b - 1 } else { break } } 502 // scan from right over the rightmost compound (stop at top-level space/'>' -- '[' depth aware) 503 var depth: i64 = 0 504 var i: i64 = b - 1 505 var split: i64 = a - 1 506 while i >= a { 507 let c: i64 = sel[i] & 0xff 508 if c == 93 { depth = depth + 1 } 509 else { if c == 91 { depth = depth - 1 } 510 else { if depth == 0 { if c == 32 { split = i; i = a - 1 } else { if c == 62 { split = i; i = a - 1 } } } } } 511 i = i - 1 512 } 513 if split < a { return dt_sel_compound(t, node, sel, a, b, scope_root) } 514 var rc: i64 = split + 1 515 while rc < b { if (sel[rc] & 0xff) == 32 { rc = rc + 1 } else { break } } 516 if dt_sel_compound(t, node, sel, rc, b, scope_root) == 0 { return 0 } 517 // combinator run = [le, split+1); child if it contains '>', else descendant 518 var le: i64 = split + 1 519 var hasChild: i64 = 0 520 while le > a { let c: i64 = sel[le - 1] & 0xff; if c == 32 { le = le - 1 } else { if c == 62 { hasChild = 1; le = le - 1 } else { break } } } 521 let nd: *DomNode = dt_node(t, node) 522 if hasChild == 1 { 523 if nd.parent == (0 - 1) { return 0 } 524 return dt_sel_complex(t, nd.parent, sel, a, le, scope_root) 525 } 526 var p: i64 = nd.parent 527 while p != (0 - 1) { if dt_sel_complex(t, p, sel, a, le, scope_root) == 1 { return 1 } let pn: *DomNode = dt_node(t, p); p = pn.parent } 528 return 0 529} 530// match a SELECTOR LIST (comma-separated) against node -- OR semantics. slen = strlen(sel). scope_root = 531// the element `:scope` refers to (the querySelectorAll context / -1 for none) so Sizzle's `:scope ul li` works. 532func dt_sel_matches(t: *DomTree, node: i64, sel: *u8, slen: i64, scope_root: i64) -> i64 { 533 var a: i64 = 0 534 var i: i64 = 0 535 var depth: i64 = 0 536 while i <= slen { 537 var atend: i64 = 0 538 if i == slen { atend = 1 } 539 var c: i64 = 0 540 if atend == 0 { c = sel[i] & 0xff } 541 if atend == 0 { if c == 91 { depth = depth + 1 } else { if c == 93 { depth = depth - 1 } } } 542 if atend == 1 { if dt_sel_complex(t, node, sel, a, i, scope_root) == 1 { return 1 } a = i + 1 } 543 else { if depth == 0 { if c == 44 { if dt_sel_complex(t, node, sel, a, i, scope_root) == 1 { return 1 } a = i + 1 } } } 544 i = i + 1 545 } 546 return 0 547} 548// whole-tree collect of nodes matching the selector list (querySelectorAll over complex selectors). 549func dt_sel_query_rec(t: *DomTree, idx: i64, sel: *u8, slen: i64, arr: *i64, cnt: *i64, cap: i64, scope_root: i64) -> i64 { 550 if idx == (0 - 1) { return 0 } 551 let nd: *DomNode = dt_node(t, idx) 552 if nd.kind == DT_ELEM { if dt_sel_matches(t, idx, sel, slen, scope_root) == 1 { if cnt[0] < cap { arr[cnt[0]] = idx; cnt[0] = cnt[0] + 1 } } } 553 var c: i64 = nd.first_child 554 while c != (0 - 1) { dt_sel_query_rec(t, c, sel, slen, arr, cnt, cap, scope_root); let cn: *DomNode = dt_node(t, c); c = cn.next_sib } 555 return 0 556} 557// collect matches into arr starting the walk at `root` subtree's CHILDREN (element-scoped) or whole tree. 558// `root` IS the `:scope` element (querySelectorAll context) -- Sizzle scopes descendant .find via `:scope ul li`. 559func dt_sel_query(t: *DomTree, root: i64, sel: *u8, slen: i64, arr: *i64, cap: i64) -> i64 { 560 let cnt: *i64 = sys_mmap(8) as *i64 561 cnt[0] = 0 562 let nd: *DomNode = dt_node(t, root) 563 var c: i64 = nd.first_child 564 while c != (0 - 1) { dt_sel_query_rec(t, c, sel, slen, arr, cnt, cap, root); let cn: *DomNode = dt_node(t, c); c = cn.next_sib } 565 return cnt[0] 566} 567func dt_query_one_rec(t: *DomTree, idx: i64, mode: i64, name: *u8) -> i64 { 568 if idx == (0 - 1) { return 0 - 1 } 569 if dt_sel_match(t, idx, mode, name) == 1 { return idx } 570 let nd: *DomNode = dt_node(t, idx) 571 var c: i64 = nd.first_child 572 while c != (0 - 1) { 573 let r: i64 = dt_query_one_rec(t, c, mode, name) 574 if r != (0 - 1) { return r } 575 let cn: *DomNode = dt_node(t, c) 576 c = cn.next_sib 577 } 578 return 0 - 1 579} 580func dt_query_one(t: *DomTree, mode: i64, name: *u8) -> i64 { return dt_query_one_rec(t, t.root, mode, name) } 581func dt_query_all_rec(t: *DomTree, idx: i64, mode: i64, name: *u8, arr: *i64, cnt: *i64, cap: i64) -> i64 { 582 if idx == (0 - 1) { return 0 } 583 if dt_sel_match(t, idx, mode, name) == 1 { if cnt[0] < cap { arr[cnt[0]] = idx; cnt[0] = cnt[0] + 1 } } 584 let nd: *DomNode = dt_node(t, idx) 585 var c: i64 = nd.first_child 586 while c != (0 - 1) { 587 dt_query_all_rec(t, c, mode, name, arr, cnt, cap) 588 let cn: *DomNode = dt_node(t, c) 589 c = cn.next_sib 590 } 591 return 0 592} 593func dt_query_all(t: *DomTree, mode: i64, name: *u8, arr: *i64, cap: i64) -> i64 { 594 let cnt: *i64 = sys_mmap(8) as *i64 595 cnt[0] = 0 596 dt_query_all_rec(t, t.root, mode, name, arr, cnt, cap) 597 return cnt[0] 598} 599 600// concat all descendant text into out (cap-guarded). returns bytes written. 601func dt_tc_rec(t: *DomTree, idx: i64, out: *u8, cap: i64, posp: *i64) -> i64 { 602 let nd: *DomNode = dt_node(t, idx) 603 if nd.kind == DT_TEXT { 604 let s: *u8 = nd.text as *u8 605 var k: i64 = 0 606 var p: i64 = posp[0] 607 while k < nd.text_len { if p < cap { out[p] = s[k]; p = p + 1 } k = k + 1 } 608 posp[0] = p 609 } 610 var c: i64 = nd.first_child 611 while c != (0 - 1) { 612 dt_tc_rec(t, c, out, cap, posp) 613 let cn: *DomNode = dt_node(t, c) 614 c = cn.next_sib 615 } 616 return 0 617} 618func dt_text_content(t: *DomTree, node: i64, out: *u8, cap: i64) -> i64 { 619 let posp: *i64 = sys_mmap(8) as *i64 620 posp[0] = 0 621 dt_tc_rec(t, node, out, cap, posp) 622 return posp[0] 623} 624 625// ---- parse attributes out of a start-tag source span ---- 626func dt_is_name(b: i64) -> i64 { 627 if b >= 97 { if b <= 122 { return 1 } } 628 if b >= 65 { if b <= 90 { return 1 } } 629 if b >= 48 { if b <= 57 { return 1 } } 630 if b == 45 { return 1 } 631 if b == 95 { return 1 } 632 if b == 58 { return 1 } 633 return 0 634} 635func dt_is_ws(b: i64) -> i64 { 636 if b == 32 { return 1 } 637 if b == 9 { return 1 } 638 if b == 10 { return 1 } 639 if b == 13 { return 1 } 640 if b == 12 { return 1 } 641 return 0 642} 643func dt_parse_attrs(t: *DomTree, node: i64, src: *u8, soff: i64, slen: i64) -> i64 { 644 let end: i64 = soff + slen 645 var i: i64 = soff + 1 // past '<' 646 while i < end { if dt_is_name(src[i] & 0xff) == 1 { i = i + 1 } else { break } } 647 // (the loop above stops at first non-name char = end of tag name) 648 while i < end { 649 while i < end { if dt_is_ws(src[i] & 0xff) == 1 { i = i + 1 } else { break } } 650 if i >= end { break } 651 let ch: i64 = src[i] & 0xff 652 if ch == 62 { break } // '>' 653 if ch == 47 { i = i + 1; continue } // '/' 654 let nstart: i64 = i 655 while i < end { if dt_is_name(src[i] & 0xff) == 1 { i = i + 1 } else { break } } 656 let nlen: i64 = i - nstart 657 if nlen == 0 { i = i + 1; continue } 658 while i < end { if dt_is_ws(src[i] & 0xff) == 1 { i = i + 1 } else { break } } 659 if i < end { if (src[i] & 0xff) == 61 { // '=' 660 i = i + 1 661 while i < end { if dt_is_ws(src[i] & 0xff) == 1 { i = i + 1 } else { break } } 662 if i < end { 663 let q: i64 = src[i] & 0xff 664 if q == 34 { i = i + 1; let vs: i64 = i; while i < end { if (src[i] & 0xff) == 34 { break } i = i + 1 } dt_set_attr_span(t, node, src, nstart, nlen, src, vs, i - vs); if i < end { i = i + 1 } } 665 else { if q == 39 { i = i + 1; let vs2: i64 = i; while i < end { if (src[i] & 0xff) == 39 { break } i = i + 1 } dt_set_attr_span(t, node, src, nstart, nlen, src, vs2, i - vs2); if i < end { i = i + 1 } } 666 else { let vs3: i64 = i; while i < end { let cc: i64 = src[i] & 0xff; if dt_is_ws(cc) == 1 { break } if cc == 62 { break } i = i + 1 } dt_set_attr_span(t, node, src, nstart, nlen, src, vs3, i - vs3) } } 667 } 668 } else { 669 dt_set_attr_span(t, node, src, nstart, nlen, "\x00" as *u8, 0, 0) // boolean attr 670 } } 671 } 672 return 0 673} 674 675// case-insensitive find of `needle` (nlen bytes) in buf[from..hlen), or -1. 676func dt_find_ci(buf: *u8, from: i64, hlen: i64, needle: *u8, nlen: i64) -> i64 { 677 if nlen <= 0 { return from } 678 var i: i64 = from 679 while (i + nlen) <= hlen { 680 var j: i64 = 0 681 var ok: i64 = 1 682 while j < nlen { 683 var a: i64 = buf[i + j] & 0xff 684 var b: i64 = needle[j] & 0xff 685 if a >= 65 { if a <= 90 { a = a + 32 } } 686 if b >= 65 { if b <= 90 { b = b + 32 } } 687 if a != b { ok = 0; j = nlen } else { j = j + 1 } 688 } 689 if ok == 1 { return i } 690 i = i + 1 691 } 692 return 0 - 1 693} 694// raw-text elements: their content is NOT html and must NOT be tokenized (RCDATA-safe round-trip). 695func dt_is_rawtext(tag: *u8) -> i64 { 696 if dt_ceq(tag, "script\x00" as *u8) == 1 { return 1 } 697 if dt_ceq(tag, "style\x00" as *u8) == 1 { return 1 } 698 return 0 699} 700func dt_rawclose(tag: *u8) -> *u8 { 701 if dt_ceq(tag, "script\x00" as *u8) == 1 { return "</script\x00" as *u8 } 702 return "</style\x00" as *u8 703} 704// ---- parse HTML -> tree (reuses nx_html_tokenizer) ---- 705func dt_parse_into(t: *DomTree, start: i64, html: *u8, hlen: i64) -> i64 { 706 let c: *HtmlCursor = sys_mmap(24) as *HtmlCursor 707 nx_html_cursor_init(c, html, hlen) 708 let tok: *HtmlToken = sys_mmap(56) as *HtmlToken 709 var cur: i64 = start 710 var go: i64 = 1 711 while go == 1 { 712 nx_html_next_token(c, tok) 713 let k: i64 = tok.kind 714 if k == NX_HTML_TOK_EOF { go = 0 } 715 else { if k == NX_HTML_TOK_START_TAG { 716 let e: i64 = dt_create_element_span(t, html, tok.name_off, tok.name_len) 717 dt_parse_attrs(t, e, html, tok.src_off, tok.src_len) 718 dt_append_child(t, cur, e) 719 let nd: *DomNode = dt_node(t, e) 720 if dt_is_rawtext(nd.tag as *u8) == 1 { 721 let close: *u8 = dt_rawclose(nd.tag as *u8) 722 let cl: i64 = dt_clen(close) 723 let ce: i64 = dt_find_ci(html, c.pos, hlen, close, cl) 724 if ce >= 0 { 725 if ce > c.pos { let rtx: i64 = dt_create_text(t, html, c.pos, ce - c.pos); dt_append_child(t, e, rtx) } 726 var gp: i64 = ce + cl 727 while gp < hlen { if (html[gp] & 0xff) == 62 { break } gp = gp + 1 } 728 if gp < hlen { gp = gp + 1 } 729 c.pos = gp 730 } else { 731 if c.pos < hlen { let rtx2: i64 = dt_create_text(t, html, c.pos, hlen - c.pos); dt_append_child(t, e, rtx2) } 732 c.pos = hlen 733 } 734 } else { 735 if nd.voidf == 0 { cur = e } 736 } 737 } else { if k == NX_HTML_TOK_SELF_CLOSING { 738 let e2: i64 = dt_create_element_span(t, html, tok.name_off, tok.name_len) 739 dt_parse_attrs(t, e2, html, tok.src_off, tok.src_len) 740 dt_append_child(t, cur, e2) 741 } else { if k == NX_HTML_TOK_END_TAG { 742 let tagc: *u8 = (dt_dup_lower(html, tok.name_off, tok.name_len)) as *u8 743 var anc: i64 = cur 744 var found: i64 = 0 - 1 745 while anc != (0 - 1) { 746 let an: *DomNode = dt_node(t, anc) 747 var matched: i64 = 0 748 if an.kind == DT_ELEM { if dt_ceq(an.tag as *u8, tagc) == 1 { matched = 1 } } 749 if matched == 1 { found = anc; anc = 0 - 1 } 750 else { anc = an.parent } 751 } 752 if found != (0 - 1) { 753 let fn: *DomNode = dt_node(t, found) 754 cur = fn.parent 755 if cur == (0 - 1) { cur = start } 756 } 757 } else { if k == NX_HTML_TOK_TEXT { 758 if tok.body_len > 0 { 759 let tx: i64 = dt_create_text(t, html, tok.body_off, tok.body_len) 760 dt_append_child(t, cur, tx) 761 } 762 } } } } 763 } 764 } 765 return 0 766} 767func dt_parse(html: *u8, hlen: i64) -> *DomTree { 768 let t: *DomTree = dt_new() 769 dt_parse_into(t, t.root, html, hlen) 770 return t 771} 772// element.innerHTML = html : drop children, parse the fragment as new children. 773func dt_set_inner_html(t: *DomTree, node: i64, html: *u8, hlen: i64) -> i64 { 774 let nd: *DomNode = dt_node(t, node) 775 nd.first_child = 0 - 1 776 nd.last_child = 0 - 1 777 dt_parse_into(t, node, html, hlen) 778 return 0 779} 780 781// ---- serialize tree -> HTML ---- 782func dt_app(out: *u8, cap: i64, posp: *i64, s: *u8, n: i64) -> i64 { 783 var p: i64 = posp[0] 784 var i: i64 = 0 785 while i < n { if p < cap { out[p] = s[i]; p = p + 1 } i = i + 1 } 786 posp[0] = p 787 return 0 788} 789func dt_app_c(out: *u8, cap: i64, posp: *i64, c: *u8) -> i64 { 790 dt_app(out, cap, posp, c, dt_clen(c)) 791 return 0 792} 793func dt_ser_rec(t: *DomTree, idx: i64, out: *u8, cap: i64, posp: *i64) -> i64 { 794 let nd: *DomNode = dt_node(t, idx) 795 if nd.kind == DT_TEXT { dt_app(out, cap, posp, nd.text as *u8, nd.text_len); return 0 } 796 if nd.kind == DT_ELEM { 797 dt_app_c(out, cap, posp, "<\x00" as *u8) 798 dt_app_c(out, cap, posp, nd.tag as *u8) 799 if nd.attrs != 0 { 800 let base: *DtAttr = nd.attrs as *DtAttr 801 var i: i64 = 0 802 while i < nd.nattrs { 803 let a: *DtAttr = dt_attr_at(base, i) 804 dt_app_c(out, cap, posp, " \x00" as *u8) 805 dt_app_c(out, cap, posp, a.name as *u8) 806 dt_app_c(out, cap, posp, "=\"\x00" as *u8) 807 dt_app_c(out, cap, posp, a.val as *u8) 808 dt_app_c(out, cap, posp, "\"\x00" as *u8) 809 i = i + 1 810 } 811 } 812 dt_app_c(out, cap, posp, ">\x00" as *u8) 813 if nd.voidf == 0 { 814 var c: i64 = nd.first_child 815 while c != (0 - 1) { 816 dt_ser_rec(t, c, out, cap, posp) 817 let cn: *DomNode = dt_node(t, c) 818 c = cn.next_sib 819 } 820 dt_app_c(out, cap, posp, "</\x00" as *u8) 821 dt_app_c(out, cap, posp, nd.tag as *u8) 822 dt_app_c(out, cap, posp, ">\x00" as *u8) 823 } 824 return 0 825 } 826 // DT_DOC: just children 827 var c2: i64 = nd.first_child 828 while c2 != (0 - 1) { 829 dt_ser_rec(t, c2, out, cap, posp) 830 let cn2: *DomNode = dt_node(t, c2) 831 c2 = cn2.next_sib 832 } 833 return 0 834} 835func dt_serialize(t: *DomTree, out: *u8, cap: i64) -> i64 { 836 let posp: *i64 = sys_mmap(8) as *i64 837 posp[0] = 0 838 dt_ser_rec(t, t.root, out, cap, posp) 839 return posp[0] 840}