1use alloc::collections::BTreeMap;
47use alloc::format;
48use alloc::string::String;
49use alloc::vec;
50use alloc::vec::Vec;
51
52use crate::md_doc::{MdDoc, MB_ROW};
53use crate::rich_html::{attrs, decode, find_ci, tag_end};
54
55pub(crate) const NIL: u32 = u32::MAX;
56const NODES_MAX: usize = 1 << 20;
58const DEPTH_MAX: usize = 256;
59const SEL_PARTS: usize = 16;
61const SEL_LIST: usize = 64;
62const SEL_BYTES: usize = 4096;
63
64pub(crate) const K_ROOT: u8 = 0;
65pub(crate) const K_ELEM: u8 = 1;
66pub(crate) const K_TEXT: u8 = 2;
67
68#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
71pub struct MdNode {
72 i: u32,
73 g: u32,
74}
75
76impl MdNode {
77 pub const NONE: MdNode = MdNode { i: 0, g: 0 };
79
80 pub fn is_none(self) -> bool {
82 self.g == 0
83 }
84
85 pub fn raw(self) -> u64 {
87 (self.g as u64) << 32 | self.i as u64
88 }
89
90 pub fn from_raw(v: u64) -> MdNode {
92 MdNode { i: v as u32, g: (v >> 32) as u32 }
93 }
94}
95
96#[derive(Clone, Debug)]
97pub(crate) struct Node {
98 pub(crate) serial: u32,
100 pub(crate) kind: u8,
101 pub(crate) parent: u32,
102 pub(crate) first: u32,
103 pub(crate) last: u32,
104 pub(crate) prev: u32,
105 pub(crate) next: u32,
106 pub(crate) name: String,
108 pub(crate) attrs: Vec<(String, String)>,
109 pub(crate) blk: u32,
111}
112
113#[derive(Clone, Debug, Default)]
115pub(crate) struct Dom {
116 pub(crate) nodes: Vec<Node>,
117 free: Vec<(u32, u32)>,
119 ids: BTreeMap<String, Vec<u32>>,
121 pub(crate) changed: bool,
124 pub(crate) touched: Option<Vec<(u32, bool)>>,
125 pub(crate) truncated: bool,
127}
128
129const TOUCHED_MAX: usize = 64;
131
132const C_INLINE: u8 = 0;
136const C_BLOCK: u8 = 1;
137const C_P: u8 = 2;
138const C_LI: u8 = 3;
139const C_DDT: u8 = 4;
140const C_TCELL: u8 = 5;
141const C_TR: u8 = 6;
142const C_TSECT: u8 = 7;
143const C_TABLE: u8 = 8;
144const C_LIST: u8 = 9;
145const C_DL: u8 = 10;
146const C_OPTION: u8 = 11;
147const C_OPTGROUP: u8 = 12;
148const C_HEAD: u8 = 13;
149const C_HEADING: u8 = 14;
150const C_BUTTON: u8 = 15;
151const C_SELECT: u8 = 16;
152
153fn code(t: &str) -> u8 {
154 match t {
155 "p" => C_P,
156 "li" => C_LI,
157 "dd" | "dt" => C_DDT,
158 "td" | "th" => C_TCELL,
159 "tr" => C_TR,
160 "thead" | "tbody" | "tfoot" => C_TSECT,
161 "table" => C_TABLE,
162 "ul" | "ol" | "menu" | "dir" => C_LIST,
163 "dl" => C_DL,
164 "option" => C_OPTION,
165 "optgroup" => C_OPTGROUP,
166 "head" => C_HEAD,
167 "h1" | "h2" | "h3" | "h4" | "h5" | "h6" => C_HEADING,
168 "button" => C_BUTTON,
169 "select" => C_SELECT,
170 "div" | "blockquote" | "pre" | "section" | "article" | "header" | "footer" | "address" | "figure" | "figcaption" | "center" | "nav"
171 | "aside" | "main" | "form" | "fieldset" | "legend" | "details" | "summary" | "dialog" | "hgroup" | "caption" | "html" | "body"
172 | "colgroup" | "listing" | "xmp" | "plaintext" => C_BLOCK,
173 _ => C_INLINE,
174 }
175}
176
177pub(crate) fn void(t: &str) -> bool {
179 matches!(t, "area" | "base" | "br" | "col" | "embed" | "hr" | "img" | "input" | "link" | "meta" | "param" | "source" | "track" | "wbr" | "keygen")
180}
181
182fn raw(t: &str) -> bool {
184 matches!(t, "script" | "style" | "textarea" | "title" | "template" | "noscript" | "svg" | "xmp")
185}
186
187fn head_tag(t: &str) -> bool {
189 matches!(t, "base" | "link" | "meta" | "title" | "style" | "script" | "noscript" | "template")
190}
191
192fn closes_p(t: &str) -> bool {
194 matches!(
195 t,
196 "address" | "article" | "aside" | "blockquote" | "center" | "details" | "dialog" | "dir" | "div" | "dl" | "fieldset" | "figcaption"
197 | "figure" | "footer" | "form" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "header" | "hgroup" | "hr" | "main" | "menu" | "nav"
198 | "ol" | "p" | "pre" | "section" | "summary" | "table" | "ul" | "li" | "dd" | "dt" | "listing" | "xmp" | "plaintext"
199 )
200}
201
202fn leaf_inside(t: &str) -> bool {
205 !matches!(
206 t,
207 "p" | "div" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" | "li" | "blockquote" | "pre" | "tr" | "dt" | "dd" | "section" | "article" | "header"
208 | "footer" | "table" | "address" | "figure" | "center" | "thead" | "tbody" | "tfoot" | "td" | "th" | "caption" | "ul" | "ol" | "dl"
209 | "br" | "hr" | "img" | "input" | "select" | "textarea" | "button" | "label" | "form" | "option" | "optgroup" | "a" | "script"
210 | "style" | "head" | "title" | "template" | "noscript" | "svg" | "meta" | "link" | "html" | "body" | "xmp" | "listing" | "plaintext"
211 | "nav" | "main" | "aside" | "fieldset" | "legend" | "details" | "summary" | "dialog" | "hgroup" | "figcaption" | "menu" | "dir"
212 )
213}
214
215fn name_ok(s: &str) -> bool {
217 !s.is_empty() && s.len() <= 64 && s.bytes().all(|b| b > b' ' && !matches!(b, b'"' | b'\'' | b'>' | b'/' | b'=' | b'<' | 0x7F))
218}
219
220impl Dom {
223 pub(crate) fn new() -> Dom {
225 let mut d = Dom { touched: Some(Vec::new()), ..Dom::default() };
226 d.nodes.push(Node { serial: 1, kind: K_ROOT, parent: NIL, first: NIL, last: NIL, prev: NIL, next: NIL, name: String::from("#document"), attrs: Vec::new(), blk: NIL });
227 d
228 }
229
230 pub(crate) fn parse(s: &str) -> Dom {
232 let mut d = Dom::new();
233 build(&mut d, 0, s);
234 d
235 }
236
237 pub(crate) fn is_html(&self) -> bool {
239 !self.nodes.is_empty()
240 }
241
242 pub(crate) fn handle(&self, i: u32) -> MdNode {
243 match self.nodes.get(i as usize) {
244 Some(n) if n.serial != 0 => MdNode { i, g: n.serial },
245 _ => MdNode::NONE,
246 }
247 }
248
249 pub(crate) fn get(&self, n: MdNode) -> Option<u32> {
251 (n.g != 0 && self.nodes.get(n.i as usize).is_some_and(|x| x.serial == n.g)).then_some(n.i)
252 }
253
254 fn live(&self) -> usize {
255 self.nodes.len() - self.free.len()
256 }
257
258 fn alloc(&mut self, kind: u8, name: String, attrs: Vec<(String, String)>) -> Option<u32> {
260 if self.live() >= NODES_MAX {
261 return None;
262 }
263 let i = match self.free.pop() {
264 Some((i, g)) => {
265 self.nodes[i as usize] = Node { serial: g, kind, parent: NIL, first: NIL, last: NIL, prev: NIL, next: NIL, name, attrs, blk: NIL };
266 i
267 }
268 None => {
269 self.nodes.push(Node { serial: 1, kind, parent: NIL, first: NIL, last: NIL, prev: NIL, next: NIL, name, attrs, blk: NIL });
270 self.nodes.len() as u32 - 1
271 }
272 };
273 if kind == K_ELEM {
274 if let Some(id) = self.id_of(i) {
275 let id = String::from(id);
276 self.ids.entry(id).or_default().push(i);
277 }
278 }
279 Some(i)
280 }
281
282 pub(crate) fn attr(&self, i: u32, k: &str) -> Option<&str> {
283 self.nodes[i as usize].attrs.iter().find(|a| a.0 == k).map(|a| a.1.as_str())
284 }
285
286 fn id_of(&self, i: u32) -> Option<&str> {
287 self.attr(i, "id").filter(|v| !v.is_empty())
288 }
289
290 fn id_drop(&mut self, i: u32) {
291 if let Some(id) = self.id_of(i) {
292 let id = String::from(id);
293 if let Some(v) = self.ids.get_mut(&id) {
294 v.retain(|&x| x != i);
295 if v.is_empty() {
296 self.ids.remove(&id);
297 }
298 }
299 }
300 }
301
302 fn id_add(&mut self, i: u32) {
303 if let Some(id) = self.id_of(i) {
304 let id = String::from(id);
305 self.ids.entry(id).or_default().push(i);
306 }
307 }
308
309 fn append(&mut self, p: u32, c: u32) {
311 self.insert_before(p, c, NIL);
312 }
313
314 fn insert_before(&mut self, p: u32, c: u32, before: u32) {
316 let prev = if before == NIL { self.nodes[p as usize].last } else { self.nodes[before as usize].prev };
317 {
318 let n = &mut self.nodes[c as usize];
319 n.parent = p;
320 n.prev = prev;
321 n.next = before;
322 }
323 if prev == NIL {
324 self.nodes[p as usize].first = c;
325 } else {
326 self.nodes[prev as usize].next = c;
327 }
328 if before == NIL {
329 self.nodes[p as usize].last = c;
330 } else {
331 self.nodes[before as usize].prev = c;
332 }
333 }
334
335 fn detach(&mut self, c: u32) {
337 let (p, prev, next) = {
338 let n = &self.nodes[c as usize];
339 (n.parent, n.prev, n.next)
340 };
341 if p == NIL {
342 return;
343 }
344 if prev == NIL {
345 self.nodes[p as usize].first = next;
346 } else {
347 self.nodes[prev as usize].next = next;
348 }
349 if next == NIL {
350 self.nodes[p as usize].last = prev;
351 } else {
352 self.nodes[next as usize].prev = prev;
353 }
354 let n = &mut self.nodes[c as usize];
355 n.parent = NIL;
356 n.prev = NIL;
357 n.next = NIL;
358 }
359
360 pub(crate) fn subtree(&self, i: u32) -> Vec<u32> {
362 let mut out = Vec::new();
363 let mut st = vec![i];
364 while let Some(x) = st.pop() {
365 out.push(x);
366 let mut k = self.nodes[x as usize].last;
367 while k != NIL {
368 st.push(k);
369 k = self.nodes[k as usize].prev;
370 }
371 }
372 out
373 }
374
375 fn free_tree(&mut self, i: u32) {
377 self.detach(i);
378 for x in self.subtree(i) {
379 if self.nodes[x as usize].kind == K_ELEM {
380 self.id_drop(x);
381 }
382 let n = &mut self.nodes[x as usize];
383 let g = n.serial.wrapping_add(1).max(1);
385 *n = Node { serial: 0, kind: K_TEXT, parent: NIL, first: NIL, last: NIL, prev: NIL, next: NIL, name: String::new(), attrs: Vec::new(), blk: NIL };
386 self.free.push((x, g));
387 }
388 }
389
390 fn unblock(&mut self, i: u32) {
392 for x in self.subtree(i) {
393 self.nodes[x as usize].blk = NIL;
394 }
395 }
396
397 pub(crate) fn attached(&self, i: u32) -> bool {
399 let mut x = i;
400 loop {
401 let n = &self.nodes[x as usize];
402 if n.kind == K_ROOT {
403 return true;
404 }
405 if n.parent == NIL {
406 return false;
407 }
408 x = n.parent;
409 }
410 }
411
412 fn within(&self, i: u32, a: u32) -> bool {
414 let mut x = i;
415 while x != NIL {
416 if x == a {
417 return true;
418 }
419 x = self.nodes[x as usize].parent;
420 }
421 false
422 }
423
424 pub(crate) fn touch(&mut self, i: u32, own: bool) {
426 self.changed = true;
427 if let Some(t) = self.touched.as_mut() {
428 if !t.contains(&(i, own)) {
429 t.push((i, own));
430 }
431 if t.len() > TOUCHED_MAX {
432 self.touched = None;
433 }
434 }
435 }
436
437 pub(crate) fn text_of(&self, i: u32) -> String {
439 let mut s = String::new();
440 for x in self.subtree(i) {
441 let n = &self.nodes[x as usize];
442 if n.kind == K_TEXT {
443 s.push_str(&n.name);
444 }
445 }
446 s
447 }
448
449 pub(crate) fn leaf_of(&self, e: u32, own: bool) -> Option<u32> {
455 let mut l = e;
456 while self.nodes[l as usize].blk == NIL {
457 l = self.nodes[l as usize].parent;
458 if l == NIL {
459 return None;
460 }
461 }
462 if l == e && own {
463 return None;
464 }
465 let mut depth = 0;
466 let mut x = self.nodes[l as usize].parent;
467 while x != NIL {
468 let n = &self.nodes[x as usize];
469 if n.kind == K_ELEM
470 && matches!(
471 n.name.as_str(),
472 "table" | "thead" | "tbody" | "tfoot" | "tr" | "td" | "th" | "caption" | "pre" | "button" | "select" | "option" | "optgroup"
473 | "textarea" | "label" | "a" | "script" | "style" | "head" | "title" | "template" | "noscript" | "svg" | "xmp" | "listing"
474 | "plaintext"
475 )
476 {
477 return None;
478 }
479 depth += 1;
480 x = n.parent;
481 }
482 let mut st = vec![(l, depth)];
484 while let Some((x, dd)) = st.pop() {
485 if dd > 48 {
486 return None;
487 }
488 let mut k = self.nodes[x as usize].first;
489 while k != NIL {
490 let n = &self.nodes[k as usize];
491 if n.kind == K_ELEM {
492 if !leaf_inside(&n.name) {
493 return None;
494 }
495 st.push((k, dd + 1));
496 }
497 k = n.next;
498 }
499 }
500 Some(l)
501 }
502
503 pub(crate) fn hidden_attr(&self, i: u32) -> bool {
505 self.nodes[i as usize].attrs.iter().any(|a| a.0 == "hidden")
506 }
507
508 pub(crate) fn has_class(&self, i: u32, c: &str) -> bool {
510 self.attr(i, "class").is_some_and(|v| v.split_ascii_whitespace().any(|w| w == c))
511 }
512
513 fn inner_html(&self, i: u32, out: &mut String) {
515 let mut st: Vec<(u32, bool)> = Vec::new();
517 let mut k = self.nodes[i as usize].last;
518 while k != NIL {
519 st.push((k, false));
520 k = self.nodes[k as usize].prev;
521 }
522 let rawp = |d: &Dom, x: u32| {
523 let p = d.nodes[x as usize].parent;
524 p != NIL && matches!(d.nodes[p as usize].name.as_str(), "script" | "style" | "template" | "noscript" | "svg" | "xmp")
525 };
526 while let Some((x, exit)) = st.pop() {
527 let n = &self.nodes[x as usize];
528 if exit {
529 out.push_str("</");
530 out.push_str(&n.name);
531 out.push('>');
532 continue;
533 }
534 if n.kind == K_TEXT {
535 if rawp(self, x) {
536 out.push_str(&n.name);
537 } else {
538 esc_html(&n.name, false, out);
539 }
540 continue;
541 }
542 out.push('<');
543 out.push_str(&n.name);
544 for (k, v) in &n.attrs {
545 out.push(' ');
546 out.push_str(k);
547 if !v.is_empty() {
548 out.push_str("=\"");
549 esc_html(v, true, out);
550 out.push('"');
551 }
552 }
553 out.push('>');
554 if void(&n.name) {
555 continue;
556 }
557 st.push((x, true));
558 let mut k = n.last;
559 while k != NIL {
560 st.push((k, false));
561 k = self.nodes[k as usize].prev;
562 }
563 }
564 }
565}
566
567fn esc_html(s: &str, attr: bool, out: &mut String) {
568 for c in s.chars() {
569 match c {
570 '&' => out.push_str("&"),
571 '<' => out.push_str("<"),
572 '>' => out.push_str(">"),
573 '"' if attr => out.push_str("""),
574 '\u{A0}' => out.push_str(" "),
575 _ => out.push(c),
576 }
577 }
578}
579
580struct Builder<'a> {
583 d: &'a mut Dom,
584 st: Vec<(u32, u8)>,
586}
587
588impl Builder<'_> {
589 fn top(&self) -> (u32, u8) {
590 self.st[self.st.len() - 1]
591 }
592
593 fn find(&self, want: impl Fn(&Dom, u32, u8) -> bool, stop: impl Fn(&Dom, u32, u8) -> bool) -> Option<usize> {
595 for k in (1..self.st.len()).rev() {
596 let (n, c) = self.st[k];
597 if want(self.d, n, c) {
598 return Some(k);
599 }
600 if stop(self.d, n, c) {
601 return None;
602 }
603 }
604 None
605 }
606
607 fn text(&mut self, t: &str) -> bool {
609 if t.is_empty() {
610 return true;
611 }
612 if self.st.len() > 1 && self.top().1 == C_HEAD && t.chars().any(|c| !c.is_whitespace()) {
613 self.st.pop();
614 }
615 let p = self.top().0;
616 self.text_into(p, t)
617 }
618
619 fn text_into(&mut self, p: u32, t: &str) -> bool {
620 let last = self.d.nodes[p as usize].last;
621 if last != NIL && self.d.nodes[last as usize].kind == K_TEXT {
622 self.d.nodes[last as usize].name.push_str(t);
623 return true;
624 }
625 let Some(x) = self.d.alloc(K_TEXT, String::from(t), Vec::new()) else { return false };
626 self.d.append(p, x);
627 true
628 }
629
630 fn open(&mut self, name: &str, at: Vec<(String, String)>, selfclose: bool) -> Option<(u32, bool)> {
632 let c = code(name);
633 let boundary = |_: &Dom, _: u32, c: u8| matches!(c, C_TABLE | C_TCELL | C_BUTTON | C_SELECT);
634 if self.st.len() > 1 && self.top().1 == C_HEAD && !head_tag(name) {
636 self.st.pop();
637 }
638 if c == C_LI || c == C_DDT {
639 let k = self.find(
640 |_, _, e| e == c,
641 |d, n, e| matches!(e, C_LIST | C_DL | C_TABLE | C_TCELL | C_BUTTON | C_SELECT) || (e == C_BLOCK && !matches!(d.nodes[n as usize].name.as_str(), "div" | "address")),
642 );
643 if let Some(k) = k {
644 self.st.truncate(k);
645 }
646 }
647 if closes_p(name) {
648 if let Some(k) = self.find(|_, _, e| e == C_P, boundary) {
649 self.st.truncate(k);
650 }
651 }
652 let tc = self.top().1;
653 if self.st.len() > 1 && ((c == C_HEADING && tc == C_HEADING) || (matches!(c, C_OPTION | C_OPTGROUP) && tc == C_OPTION)) {
654 self.st.pop();
655 }
656 if c == C_OPTGROUP && self.st.len() > 1 && self.top().1 == C_OPTGROUP {
657 self.st.pop();
658 }
659 let table_part = match c {
660 C_TR => Some(self.find(|_, _, e| e == C_TR, |_, _, e| matches!(e, C_TABLE | C_TSECT))),
661 C_TCELL => Some(self.find(|_, _, e| e == C_TCELL, |_, _, e| matches!(e, C_TR | C_TABLE | C_TSECT))),
662 C_TSECT => Some(self.find(|_, _, e| e == C_TSECT, |_, _, e| e == C_TABLE)),
663 _ => None,
664 };
665 if let Some(Some(k)) = table_part {
666 self.st.truncate(k);
667 }
668 let x = self.d.alloc(K_ELEM, String::from(name), at)?;
669 let p = self.top().0;
670 self.d.append(p, x);
671 if void(name) || selfclose {
672 return Some((x, false));
673 }
674 if self.st.len() < DEPTH_MAX {
675 self.st.push((x, c));
676 }
677 Some((x, raw(name)))
678 }
679
680 fn close(&mut self, name: &str) {
682 if void(name) || name == "body" || name == "html" {
683 return;
684 }
685 let c = code(name);
686 let boundary = |_: &Dom, _: u32, e: u8| matches!(e, C_TABLE | C_TCELL | C_BUTTON | C_SELECT);
687 if c == C_P && self.find(|_, _, e| e == C_P, boundary).is_none() {
688 if let Some(x) = self.d.alloc(K_ELEM, String::from("p"), Vec::new()) {
690 let p = self.top().0;
691 self.d.append(p, x);
692 }
693 return;
694 }
695 let k = self.find(
696 |d, n, _| d.nodes[n as usize].name == name,
697 |_, _, e| {
698 if c == C_INLINE {
699 e != C_INLINE
701 } else {
702 match e {
703 C_TCELL => !matches!(c, C_TR | C_TSECT | C_TABLE),
704 C_TABLE | C_BUTTON | C_SELECT => true,
705 _ => false,
706 }
707 }
708 },
709 );
710 if let Some(k) = k {
711 self.st.truncate(k);
712 }
713 }
714}
715
716fn build(d: &mut Dom, parent: u32, s: &str) {
718 let mut b = Builder { d, st: vec![(parent, C_BLOCK)] };
719 let mut rest = s;
720 let mut raw_of: Option<u32> = None;
722 loop {
723 if let Some(x) = raw_of.take() {
724 let tag = b.d.nodes[x as usize].name.clone();
725 let close = format!("</{}", tag);
726 let (content, after) = match find_ci(rest, &close) {
727 Some(i) => (&rest[..i], &rest[i..]),
728 None => (rest, ""),
729 };
730 let ok = if tag == "textarea" || tag == "title" { b.text_into(x, &decode(content)) } else { content.is_empty() || b.text_into(x, content) };
731 if !ok {
732 b.d.truncated = true;
733 return;
734 }
735 rest = after;
736 }
737 if rest.is_empty() {
738 return;
739 }
740 let Some(lt) = rest.find('<') else {
741 if !b.text(&decode(rest)) {
742 b.d.truncated = true;
743 }
744 return;
745 };
746 if lt > 0 && !b.text(&decode(&rest[..lt])) {
747 b.d.truncated = true;
748 return;
749 }
750 rest = &rest[lt..];
751 if rest.starts_with("<!--") {
752 rest = rest.find("-->").map_or("", |j| &rest[j + 3..]);
753 continue;
754 }
755 if rest.starts_with("<!") || rest.starts_with("<?") {
756 rest = rest.find('>').map_or("", |j| &rest[j + 1..]);
757 continue;
758 }
759 let Some(gt) = tag_end(rest) else {
760 if !b.text(&decode(rest)) {
761 b.d.truncated = true;
762 }
763 return;
764 };
765 let inner = &rest[1..gt];
766 rest = &rest[gt + 1..];
767 if let Some(name) = inner.strip_prefix('/') {
768 let name = name.trim().split_whitespace().next().unwrap_or("").to_ascii_lowercase();
769 b.close(&name);
770 continue;
771 }
772 let name_end = inner.find(|c: char| c.is_whitespace() || c == '/').unwrap_or(inner.len());
773 let name = inner[..name_end].to_ascii_lowercase();
774 if name.is_empty() || !name.as_bytes()[0].is_ascii_alphabetic() {
775 if !b.text(&format!("<{}>", decode(inner))) {
776 b.d.truncated = true;
777 return;
778 }
779 continue;
780 }
781 let selfclose = inner.trim_end().ends_with('/');
782 let at = attrs(&inner[name_end..]);
783 match b.open(&name, at, selfclose) {
784 None => {
785 b.d.truncated = true;
786 return;
787 }
788 Some((x, true)) => raw_of = Some(x),
789 Some(_) => {}
790 }
791 }
792}
793
794#[derive(Clone, Debug, Default)]
798pub(crate) struct Compound {
799 tag: String,
800 id: String,
801 classes: Vec<String>,
802 attrs: Vec<(String, u8, String)>,
803}
804
805#[derive(Clone, Debug, Default)]
808pub(crate) struct Sel {
809 parts: Vec<Compound>,
810 child: Vec<bool>,
811}
812
813pub(crate) enum SelKey<'a> {
815 Id(&'a str),
816 Class(&'a str),
817 Tag(&'a str),
818 Any,
819}
820
821impl Sel {
822 pub(crate) fn spec(&self) -> u32 {
824 let (mut ids, mut cls, mut tags) = (0u32, 0u32, 0u32);
825 for p in &self.parts {
826 ids += (!p.id.is_empty()) as u32;
827 cls += (p.classes.len() + p.attrs.len()) as u32;
828 tags += (!p.tag.is_empty()) as u32;
829 }
830 ids.min(255) << 16 | cls.min(255) << 8 | tags.min(255)
831 }
832
833 pub(crate) fn key(&self) -> SelKey<'_> {
835 let s = &self.parts[0];
836 if !s.id.is_empty() {
837 SelKey::Id(&s.id)
838 } else if let Some(c) = s.classes.first() {
839 SelKey::Class(c)
840 } else if !s.tag.is_empty() {
841 SelKey::Tag(&s.tag)
842 } else {
843 SelKey::Any
844 }
845 }
846}
847
848fn ident_char(c: char) -> bool {
849 c.is_ascii_alphanumeric() || c == '-' || c == '_' || c as u32 > 127
850}
851
852fn ident(s: &str, i: usize) -> Option<(&str, usize)> {
854 let n = s[i..].char_indices().find(|&(_, c)| !ident_char(c)).map_or(s.len() - i, |(k, _)| k);
855 (n > 0).then(|| (&s[i..i + n], i + n))
856}
857
858pub(crate) fn parse_sels(s: &str) -> Option<Vec<Sel>> {
860 if s.len() > SEL_BYTES {
861 return None;
862 }
863 let mut out = Vec::new();
864 for part in split_list(s)? {
865 if out.len() >= SEL_LIST {
866 return None;
867 }
868 out.push(parse_sel(part.trim())?);
869 }
870 (!out.is_empty()).then_some(out)
871}
872
873fn split_list(s: &str) -> Option<Vec<&str>> {
875 let mut out = Vec::new();
876 let (mut start, mut q, mut br) = (0, 0u8, false);
877 for (i, b) in s.bytes().enumerate() {
878 if q != 0 {
879 if b == q {
880 q = 0;
881 }
882 continue;
883 }
884 match b {
885 b'"' | b'\'' => q = b,
886 b'[' => br = true,
887 b']' => br = false,
888 b',' if !br => {
889 out.push(&s[start..i]);
890 start = i + 1;
891 }
892 _ => {}
893 }
894 }
895 if q != 0 || br {
896 return None;
897 }
898 out.push(&s[start..]);
899 Some(out)
900}
901
902fn parse_sel(s: &str) -> Option<Sel> {
903 let b = s.as_bytes();
904 let mut parts: Vec<Compound> = Vec::new();
905 let mut child: Vec<bool> = Vec::new();
906 let mut i = 0;
907 let mut comb = 0u8;
909 while i < b.len() {
910 let c = b[i];
911 if c.is_ascii_whitespace() {
912 if comb == 0 && !parts.is_empty() {
913 comb = 1;
914 }
915 i += 1;
916 continue;
917 }
918 if c == b'>' {
919 if parts.is_empty() || comb == 2 {
920 return None;
921 }
922 comb = 2;
923 i += 1;
924 continue;
925 }
926 if !parts.is_empty() && comb == 0 {
927 return None;
928 }
929 if parts.len() >= SEL_PARTS {
930 return None;
931 }
932 let (cp, e) = parse_compound(s, i)?;
933 if !parts.is_empty() {
934 child.push(comb == 2);
935 }
936 parts.push(cp);
937 comb = 0;
938 i = e;
939 }
940 if parts.is_empty() || comb == 2 {
941 return None;
942 }
943 parts.reverse();
944 child.reverse();
945 Some(Sel { parts, child })
946}
947
948fn parse_compound(s: &str, mut i: usize) -> Option<(Compound, usize)> {
950 let b = s.as_bytes();
951 let mut c = Compound::default();
952 let start = i;
953 if b[i] == b'*' {
954 i += 1;
955 } else if let Some((t, e)) = ident(s, i) {
956 c.tag = t.to_ascii_lowercase();
957 i = e;
958 }
959 while i < b.len() {
960 match b[i] {
961 b'#' => {
962 let (t, e) = ident(s, i + 1)?;
963 if !c.id.is_empty() && c.id != t {
964 c.classes.push(String::from(" "));
966 }
967 c.id = String::from(t);
968 i = e;
969 }
970 b'.' => {
971 let (t, e) = ident(s, i + 1)?;
972 c.classes.push(String::from(t));
973 i = e;
974 }
975 b'[' => {
976 let close = s[i..].find(']')? + i;
977 let inner = s[i + 1..close].trim();
978 let ops = ["~=", "|=", "^=", "$=", "*=", "="];
979 let mut found = None;
980 for (k, op) in ops.iter().enumerate() {
981 if let Some(p) = inner.find(op) {
982 found = Some((p, k as u8 + 1, op.len()));
983 break;
984 }
985 }
986 let (name, op, val) = match found {
987 Some((p, k, l)) => {
988 let v = inner[p + l..].trim();
989 let v = if v.len() >= 2 && (v.starts_with('"') && v.ends_with('"') || v.starts_with('\'') && v.ends_with('\'')) { &v[1..v.len() - 1] } else { v };
990 (inner[..p].trim(), k, String::from(v))
991 }
992 None => (inner, 0, String::new()),
993 };
994 if !name_ok(name) || !name.chars().all(ident_char) {
995 return None;
996 }
997 c.attrs.push((name.to_ascii_lowercase(), op, val));
998 i = close + 1;
999 }
1000 b' ' | b'\t' | b'\n' | b'\r' | b'\x0C' | b'>' => break,
1001 _ => return None,
1002 }
1003 }
1004 (i > start).then_some((c, i))
1005}
1006
1007fn takes(d: &Dom, i: u32, c: &Compound) -> bool {
1009 let n = &d.nodes[i as usize];
1010 if n.kind != K_ELEM || (!c.tag.is_empty() && c.tag != n.name) {
1011 return false;
1012 }
1013 if !c.id.is_empty() && d.attr(i, "id") != Some(c.id.as_str()) {
1014 return false;
1015 }
1016 if !c.classes.iter().all(|k| d.has_class(i, k)) {
1017 return false;
1018 }
1019 c.attrs.iter().all(|(k, op, v)| {
1020 let Some(a) = d.attr(i, k) else { return false };
1021 match op {
1022 0 => true,
1023 1 => a.split_ascii_whitespace().any(|w| w == v),
1024 2 => a == v || (a.starts_with(v.as_str()) && a.as_bytes().get(v.len()) == Some(&b'-')),
1025 3 => !v.is_empty() && a.starts_with(v.as_str()),
1026 4 => !v.is_empty() && a.ends_with(v.as_str()),
1027 5 => !v.is_empty() && a.contains(v.as_str()),
1028 _ => a == v,
1029 }
1030 })
1031}
1032
1033pub(crate) fn sel_match(d: &Dom, i: u32, s: &Sel, budget: &mut usize) -> bool {
1035 let n = s.parts.len();
1036 if n == 0 {
1037 return false;
1038 }
1039 let run = |k: usize| -> usize {
1041 let mut e = k;
1042 while e + 1 < n && s.child[e] {
1043 e += 1;
1044 }
1045 e
1046 };
1047 let chain = |x: u32, k: usize, e: usize, budget: &mut usize| -> Option<u32> {
1049 let mut y = x;
1050 for j in k..=e {
1051 if j > k {
1052 y = d.nodes[y as usize].parent;
1053 }
1054 if y == NIL || *budget == 0 {
1055 return None;
1056 }
1057 *budget -= 1;
1058 if !takes(d, y, &s.parts[j]) {
1059 return None;
1060 }
1061 }
1062 Some(y)
1063 };
1064 let e = run(0);
1066 let Some(mut at) = chain(i, 0, e, budget) else { return false };
1067 let mut k = e + 1;
1068 while k < n {
1070 let e = run(k);
1071 let mut x = d.nodes[at as usize].parent;
1072 let mut found = None;
1073 while x != NIL && *budget > 0 {
1074 if let Some(y) = chain(x, k, e, budget) {
1075 found = Some(y);
1076 break;
1077 }
1078 x = d.nodes[x as usize].parent;
1079 }
1080 match found {
1081 Some(y) => at = y,
1082 None => return false,
1083 }
1084 k = e + 1;
1085 }
1086 true
1087}
1088
1089fn select_below(d: &Dom, top: u32, sels: &[Sel], first: bool) -> Vec<u32> {
1091 let mut out = Vec::new();
1092 let mut budget = d.nodes.len().saturating_mul(64).saturating_add(1 << 16);
1093 let all = d.subtree(top);
1094 for &x in all.iter().skip(1) {
1095 if d.nodes[x as usize].kind != K_ELEM {
1096 continue;
1097 }
1098 if sels.iter().any(|s| sel_match(d, x, s, &mut budget)) {
1099 out.push(x);
1100 if first {
1101 break;
1102 }
1103 }
1104 if budget == 0 {
1105 break;
1106 }
1107 }
1108 out
1109}
1110
1111impl MdDoc {
1114 fn node(&self, n: MdNode) -> Option<u32> {
1115 self.dom.get(n)
1116 }
1117
1118 pub(crate) fn elem(&self, n: MdNode) -> Option<u32> {
1119 self.node(n).filter(|&i| self.dom.nodes[i as usize].kind != K_TEXT)
1120 }
1121
1122 pub fn root(&self) -> MdNode {
1125 self.dom.handle(0)
1126 }
1127
1128 pub fn dom_size(&self) -> (usize, usize) {
1131 let live = self.dom.nodes.iter().filter(|n| n.serial != 0);
1132 live.fold((0, 0), |(c, b), n| (c + 1, b + n.name.len() + n.attrs.iter().map(|a| a.0.len() + a.1.len()).sum::<usize>()))
1133 }
1134
1135 pub fn element_at(&self, k: usize, byte: usize) -> MdNode {
1142 let Some(b) = self.blocks().get(k) else { return MdNode::NONE };
1143 let Some(top) = self.node(b.node) else { return MdNode::NONE };
1144 let want: Vec<char> = b.text.chars().filter(|c| !c.is_whitespace()).collect();
1145 let upto = b.text.get(..byte).map_or(usize::MAX, |s| s.chars().filter(|c| !c.is_whitespace()).count());
1146 let row = b.kind == MB_ROW;
1147 let mut run: Vec<(char, u32)> = Vec::with_capacity(want.len());
1149 let lined_up = |run: &[(char, u32)]| run.len() == want.len() && run.iter().zip(&want).all(|(a, w)| a.0 == *w);
1150 let mut st: Vec<u32> = Vec::new();
1151 let d = &self.dom;
1152 let mut k = d.nodes[top as usize].last;
1153 while k != NIL {
1154 st.push(k);
1155 k = d.nodes[k as usize].prev;
1156 }
1157 while let Some(x) = st.pop() {
1158 let n = &d.nodes[x as usize];
1159 if n.kind == K_TEXT {
1160 run.extend(n.name.chars().filter(|c| !c.is_whitespace()).map(|c| (c, n.parent)));
1161 continue;
1162 }
1163 if d.hidden_attr(x) || n.attrs.iter().any(|a| a.0 == "style" && a.1.replace(' ', "").contains("display:none")) {
1164 continue;
1165 }
1166 match n.name.as_str() {
1167 "script" | "style" | "title" | "head" | "template" | "noscript" | "meta" | "link" | "base" | "wbr" | "br" => continue,
1168 "input" if n.attrs.iter().any(|a| a.0 == "type" && a.1.eq_ignore_ascii_case("hidden")) => continue,
1169 "img" | "input" | "select" | "textarea" | "button" => {
1170 run.push(('\u{FFFC}', x));
1171 continue;
1172 }
1173 t if !row && code(t) != C_INLINE => {
1174 if lined_up(&run) {
1176 break;
1177 }
1178 run.clear();
1179 continue;
1180 }
1181 _ => {}
1182 }
1183 let mut k = n.last;
1184 while k != NIL {
1185 st.push(k);
1186 k = d.nodes[k as usize].prev;
1187 }
1188 }
1189 if !lined_up(&run) {
1190 return b.node;
1191 }
1192 run.get(upto).map_or(b.node, |c| d.handle(c.1))
1193 }
1194
1195 pub fn by_id(&self, id: &str) -> Option<MdNode> {
1197 let v = self.dom.ids.get(id)?;
1198 let live: Vec<u32> = v.iter().copied().filter(|&i| self.dom.attached(i)).collect();
1199 match live.len() {
1200 0 => None,
1201 1 => Some(self.dom.handle(live[0])),
1202 _ => self.dom.subtree(0).into_iter().find(|x| live.contains(x)).map(|i| self.dom.handle(i)),
1204 }
1205 }
1206
1207 pub fn select(&self, sel: &str) -> Vec<MdNode> {
1210 self.select_in(self.root(), sel)
1211 }
1212
1213 pub fn select_first(&self, sel: &str) -> Option<MdNode> {
1215 let (Some(top), Some(s)) = (self.node(self.root()), parse_sels(sel)) else { return None };
1216 select_below(&self.dom, top, &s, true).first().map(|&i| self.dom.handle(i))
1217 }
1218
1219 pub fn select_in(&self, n: MdNode, sel: &str) -> Vec<MdNode> {
1221 let (Some(top), Some(s)) = (self.node(n), parse_sels(sel)) else { return Vec::new() };
1222 select_below(&self.dom, top, &s, false).into_iter().map(|i| self.dom.handle(i)).collect()
1223 }
1224
1225 pub fn matches(&self, n: MdNode, sel: &str) -> bool {
1227 let (Some(i), Some(s)) = (self.elem(n), parse_sels(sel)) else { return false };
1228 let mut budget = 1 << 16;
1229 s.iter().any(|x| sel_match(&self.dom, i, x, &mut budget))
1230 }
1231
1232 pub fn parent(&self, n: MdNode) -> Option<MdNode> {
1234 let i = self.node(n)?;
1235 let p = self.dom.nodes[i as usize].parent;
1236 (p != NIL).then(|| self.dom.handle(p))
1237 }
1238
1239 pub fn children(&self, n: MdNode) -> Vec<MdNode> {
1241 let mut out = Vec::new();
1242 if let Some(i) = self.node(n) {
1243 let mut k = self.dom.nodes[i as usize].first;
1244 while k != NIL {
1245 out.push(self.dom.handle(k));
1246 k = self.dom.nodes[k as usize].next;
1247 }
1248 }
1249 out
1250 }
1251
1252 pub fn next_sibling(&self, n: MdNode) -> Option<MdNode> {
1254 let i = self.node(n)?;
1255 let k = self.dom.nodes[i as usize].next;
1256 (k != NIL).then(|| self.dom.handle(k))
1257 }
1258
1259 pub fn prev_sibling(&self, n: MdNode) -> Option<MdNode> {
1261 let i = self.node(n)?;
1262 let k = self.dom.nodes[i as usize].prev;
1263 (k != NIL).then(|| self.dom.handle(k))
1264 }
1265
1266 pub fn tag(&self, n: MdNode) -> &str {
1268 match self.node(n) {
1269 Some(i) if self.dom.nodes[i as usize].kind != K_TEXT => &self.dom.nodes[i as usize].name,
1270 _ => "",
1271 }
1272 }
1273
1274 pub fn is_text(&self, n: MdNode) -> bool {
1276 self.node(n).is_some_and(|i| self.dom.nodes[i as usize].kind == K_TEXT)
1277 }
1278
1279 pub fn alive(&self, n: MdNode) -> bool {
1281 self.node(n).is_some()
1282 }
1283
1284 pub fn attached(&self, n: MdNode) -> bool {
1286 self.node(n).is_some_and(|i| self.dom.attached(i))
1287 }
1288
1289 pub fn attr(&self, n: MdNode, name: &str) -> Option<&str> {
1291 let i = self.elem(n)?;
1292 self.dom.attr(i, &name.to_ascii_lowercase())
1293 }
1294
1295 pub fn attrs(&self, n: MdNode) -> &[(String, String)] {
1297 match self.elem(n) {
1298 Some(i) => &self.dom.nodes[i as usize].attrs,
1299 None => &[],
1300 }
1301 }
1302
1303 pub fn has_class(&self, n: MdNode, c: &str) -> bool {
1305 self.elem(n).is_some_and(|i| self.dom.has_class(i, c))
1306 }
1307
1308 pub fn text_of(&self, n: MdNode) -> String {
1310 self.node(n).map_or(String::new(), |i| self.dom.text_of(i))
1311 }
1312
1313 pub fn inner_html(&self, n: MdNode) -> String {
1315 let mut s = String::new();
1316 if let Some(i) = self.elem(n) {
1317 self.dom.inner_html(i, &mut s);
1318 }
1319 s
1320 }
1321
1322 pub fn changed(&self) -> bool {
1324 self.dom.changed
1325 }
1326
1327 pub fn set_text(&mut self, n: MdNode, s: &str) -> bool {
1331 let Some(i) = self.node(n) else { return false };
1332 if self.dom.nodes[i as usize].kind == K_TEXT {
1333 if self.dom.nodes[i as usize].name != s {
1334 self.dom.nodes[i as usize].name = String::from(s);
1335 let p = self.dom.nodes[i as usize].parent;
1336 if p != NIL {
1337 self.dom.touch(p, false);
1338 }
1339 }
1340 return true;
1341 }
1342 let f = self.dom.nodes[i as usize].first;
1344 if f != NIL && f == self.dom.nodes[i as usize].last && self.dom.nodes[f as usize].kind == K_TEXT && self.dom.nodes[f as usize].name == s {
1345 return true;
1346 }
1347 let mut k = self.dom.nodes[i as usize].first;
1348 while k != NIL {
1349 let next = self.dom.nodes[k as usize].next;
1350 self.dom.free_tree(k);
1351 k = next;
1352 }
1353 if !s.is_empty() {
1354 match self.dom.alloc(K_TEXT, String::from(s), Vec::new()) {
1355 Some(t) => self.dom.append(i, t),
1356 None => {
1357 self.dom.touch(i, false);
1358 return false;
1359 }
1360 }
1361 }
1362 self.dom.touch(i, false);
1363 true
1364 }
1365
1366 pub fn set_attr(&mut self, n: MdNode, name: &str, value: &str) -> bool {
1368 let Some(i) = self.elem(n) else { return false };
1369 if self.dom.nodes[i as usize].kind == K_ROOT || !name_ok(name) {
1370 return false;
1371 }
1372 let name = name.to_ascii_lowercase();
1373 if self.dom.attr(i, &name) == Some(value) {
1374 return true;
1375 }
1376 if name == "id" {
1377 self.dom.id_drop(i);
1378 }
1379 let at = &mut self.dom.nodes[i as usize].attrs;
1380 match at.iter_mut().find(|a| a.0 == name) {
1381 Some(a) => a.1 = String::from(value),
1382 None => at.push((name.clone(), String::from(value))),
1383 }
1384 if name == "id" {
1385 self.dom.id_add(i);
1386 }
1387 self.dom.touch(i, true);
1388 true
1389 }
1390
1391 pub fn remove_attr(&mut self, n: MdNode, name: &str) -> bool {
1393 let Some(i) = self.elem(n) else { return false };
1394 let name = name.to_ascii_lowercase();
1395 if self.dom.attr(i, &name).is_none() {
1396 return false;
1397 }
1398 if name == "id" {
1399 self.dom.id_drop(i);
1400 }
1401 self.dom.nodes[i as usize].attrs.retain(|a| a.0 != name);
1402 self.dom.touch(i, true);
1403 true
1404 }
1405
1406 pub fn add_class(&mut self, n: MdNode, c: &str) -> bool {
1408 let Some(i) = self.elem(n) else { return false };
1409 if c.is_empty() || c.contains(|x: char| x.is_ascii_whitespace()) {
1410 return false;
1411 }
1412 if self.dom.has_class(i, c) {
1413 return true;
1414 }
1415 let v = match self.dom.attr(i, "class") {
1416 Some(v) if !v.trim().is_empty() => format!("{} {}", v.trim(), c),
1417 _ => String::from(c),
1418 };
1419 self.set_attr(n, "class", &v)
1420 }
1421
1422 pub fn remove_class(&mut self, n: MdNode, c: &str) -> bool {
1424 let Some(i) = self.elem(n) else { return false };
1425 if !self.dom.has_class(i, c) {
1426 return false;
1427 }
1428 let v: Vec<&str> = self.dom.attr(i, "class").unwrap_or("").split_ascii_whitespace().filter(|w| *w != c).collect();
1429 let v = v.join(" ");
1430 self.set_attr(n, "class", &v)
1431 }
1432
1433 pub fn toggle_class(&mut self, n: MdNode, c: &str) -> bool {
1435 if self.has_class(n, c) {
1436 self.remove_class(n, c);
1437 false
1438 } else {
1439 self.add_class(n, c)
1440 }
1441 }
1442
1443 pub fn create_element(&mut self, tag: &str) -> MdNode {
1446 if !self.dom.is_html() || !name_ok(tag) || !tag.as_bytes()[0].is_ascii_alphabetic() {
1447 return MdNode::NONE;
1448 }
1449 match self.dom.alloc(K_ELEM, tag.to_ascii_lowercase(), Vec::new()) {
1450 Some(i) => self.dom.handle(i),
1451 None => MdNode::NONE,
1452 }
1453 }
1454
1455 pub fn create_text(&mut self, s: &str) -> MdNode {
1457 if !self.dom.is_html() {
1458 return MdNode::NONE;
1459 }
1460 match self.dom.alloc(K_TEXT, String::from(s), Vec::new()) {
1461 Some(i) => self.dom.handle(i),
1462 None => MdNode::NONE,
1463 }
1464 }
1465
1466 pub fn insert(&mut self, parent: MdNode, before: Option<MdNode>, n: MdNode) -> bool {
1470 let (Some(p), Some(c)) = (self.elem(parent), self.node(n)) else { return false };
1471 if self.dom.nodes[c as usize].kind == K_ROOT || self.dom.within(p, c) {
1472 return false;
1473 }
1474 let b = match before {
1475 Some(b) => match self.node(b) {
1476 Some(b) if self.dom.nodes[b as usize].parent == p && b != c => b,
1477 _ => return false,
1478 },
1479 None => NIL,
1480 };
1481 let old = self.dom.nodes[c as usize].parent;
1482 if old != NIL {
1483 self.dom.detach(c);
1484 self.dom.touch(old, false);
1485 }
1486 self.dom.unblock(c);
1487 self.dom.insert_before(p, c, b);
1488 self.dom.touch(p, false);
1489 true
1490 }
1491
1492 pub fn remove(&mut self, n: MdNode) -> bool {
1494 let Some(c) = self.node(n) else { return false };
1495 let p = self.dom.nodes[c as usize].parent;
1496 if p == NIL {
1497 return false;
1498 }
1499 self.dom.detach(c);
1500 self.dom.unblock(c);
1501 self.dom.touch(p, false);
1502 true
1503 }
1504
1505 pub fn free(&mut self, n: MdNode) -> bool {
1507 let Some(c) = self.node(n) else { return false };
1508 if self.dom.nodes[c as usize].kind == K_ROOT {
1509 return false;
1510 }
1511 let p = self.dom.nodes[c as usize].parent;
1512 self.dom.free_tree(c);
1513 if p != NIL {
1514 self.dom.touch(p, false);
1515 }
1516 true
1517 }
1518
1519 pub fn set_inner_html(&mut self, n: MdNode, html: &str) -> bool {
1522 let Some(i) = self.elem(n) else { return false };
1523 if raw(&self.dom.nodes[i as usize].name) {
1524 return self.set_text(n, html);
1525 }
1526 let mut k = self.dom.nodes[i as usize].first;
1527 while k != NIL {
1528 let next = self.dom.nodes[k as usize].next;
1529 self.dom.free_tree(k);
1530 k = next;
1531 }
1532 let t = self.dom.truncated;
1533 build(&mut self.dom, i, html);
1534 let cut = self.dom.truncated && !t;
1535 self.dom.truncated = t;
1536 self.dom.touch(i, false);
1537 !cut
1538 }
1539}