Skip to main content

ui/
md_dom.rs

1// md_dom -- the elements of an HTML document of the viewer (MdDoc::html, md_doc.rs): a tree whose
2// nodes keep their identity across changes, queries by simple selectors (also the selectors of
3// the style sheets, rich_html.rs), changes of text, attributes, classes and structure; the
4// blocks follow at MdDoc::update (md_view.rs does it every frame)
5//
6//   doc.root() -> MdNode                 the document: its children are the HTML's top level
7//   doc.by_id(id) -> Option<MdNode>      the first element in the document with this id (an
8//                                        index: O(log n))
9//   doc.select(sel) / select_first(sel)  the elements of the document a selector list matches,
10//                                        in document order
11//   doc.select_in(n, sel) / matches(n, sel)   among n's descendants / n itself
12//   doc.parent(n) / children(n) / next_sibling(n) / prev_sibling(n)
13//   doc.tag(n) ("" for a text) / is_text(n) / alive(n) / attached(n) (in the document)
14//   doc.attr(n, name) / attrs(n) / has_class(n, c) / text_of(n) (all its text, textContent) /
15//   inner_html(n)
16//   changes (true / a node: done; false / MdNode::NONE: a node that is gone, a Markdown
17//   document, a name that is none, the cap of nodes):
18//   doc.set_text(n, s) (an element's children become one text; they are freed) /
19//   set_attr(n, name, value) / remove_attr(n, name) / add_class(n, c) / remove_class(n, c) /
20//   toggle_class(n, c) -> on / create_element(tag) / create_text(s) (not in the document yet) /
21//   insert(parent, before, n) (before: None = at the end; a node in the document moves) /
22//   remove(n) (out of the document, kept: it may go in again) / free(n) (gone: its handles and
23//   those of its descendants refer to nothing) / set_inner_html(n, html)
24//   MdNode::raw() / MdNode::from_raw(v): a handle as a number (the objects of a script host)
25//   doc.element_at(block, byte) -> the innermost element around that byte of a block's text (an
26//   inline element, else MdBlock::node; a click: MdEv::click)
27//   doc.dom_size() -> (live nodes, bytes of their names, texts and attributes): what a host that
28//   lets others change the tree limits
29//
30// The tree: HTML read the way browsers do, simplified -- void elements (br, img, input ...),
31// raw text (script, style, textarea, title, template, noscript, svg), a <p> closed by a block,
32// <li> by <li>, <dd> / <dt>, <td> / <th>, <tr>, <option> by their like, <head> by the first
33// element of the body; an end tag closes the elements above its own, but not across a table
34// cell or a button, and an inline element's end tag does not close a block (it is dropped); a
35// stray </p> is an empty paragraph; comments go.  Depth (DEPTH_MAX open elements: deeper ones
36// stay empty, their content follows them) and nodes (NODES_MAX: the rest is cut, truncated)
37// are capped, so parsing stays linear.
38//
39// Selectors: a list (a, b) of compounds -- a tag or *, #id, .class, [attr], [attr=v] (also ~=
40// |= ^= $= *=, values in quotes or not) -- joined by descendant (blank) and child (>)
41// combinators; tags and attribute names ignore case, ids, classes and values do not.  Anything
42// else (pseudo-classes, sibling combinators, escapes) makes the selector match nothing.  A
43// match costs O(depth x compounds) per element (each run of child combinators is matched at
44// the nearest ancestor that takes it), a query at most a budget proportional to the tree.
45
46use 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;
56// live nodes, open elements at most
57const NODES_MAX: usize = 1 << 20;
58const DEPTH_MAX: usize = 256;
59// compounds of a selector, selectors of a list, bytes of a selector list
60const 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/// a node of an HTML document's element tree (an element or a text): the same across changes
69/// of the document; a freed node's handle refers to nothing (MdDoc::alive)
70#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
71pub struct MdNode {
72    i: u32,
73    g: u32,
74}
75
76impl MdNode {
77    /// no node
78    pub const NONE: MdNode = MdNode { i: 0, g: 0 };
79
80    /// it is MdNode::NONE
81    pub fn is_none(self) -> bool {
82        self.g == 0
83    }
84
85    /// the handle as a number (0: none), e.g. for a script host's objects
86    pub fn raw(self) -> u64 {
87        (self.g as u64) << 32 | self.i as u64
88    }
89
90    /// the handle of raw()
91    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    // the generation: 0 a free slot (a handle with another one is stale)
99    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    // an element's tag (lower case), a text's text
107    pub(crate) name: String,
108    pub(crate) attrs: Vec<(String, String)>,
109    // derived (MdDoc::update): the one block this block element shows by itself (NIL: none)
110    pub(crate) blk: u32,
111}
112
113/// the element tree of an HTML document (see the module header)
114#[derive(Clone, Debug, Default)]
115pub(crate) struct Dom {
116    pub(crate) nodes: Vec<Node>,
117    // free slots and the generation their next node gets
118    free: Vec<(u32, u32)>,
119    // elements with an id (in the document or not)
120    ids: BTreeMap<String, Vec<u32>>,
121    // the tree changed since the blocks were derived; the elements whose content (false) or
122    // own attributes (true) changed -- None: too many, every block again
123    pub(crate) changed: bool,
124    pub(crate) touched: Option<Vec<(u32, bool)>>,
125    // the cap of nodes cut the input
126    pub(crate) truncated: bool,
127}
128
129// how many touched elements a change may name before it counts as everything
130const TOUCHED_MAX: usize = 64;
131
132// ------------------------------------------------------------------ tags
133
134// a tag's part in building the tree
135const 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
177/// elements without content
178pub(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
182// elements whose content is text up to their end tag
183fn raw(t: &str) -> bool {
184    matches!(t, "script" | "style" | "textarea" | "title" | "template" | "noscript" | "svg" | "xmp")
185}
186
187// elements of the head (others end it)
188fn head_tag(t: &str) -> bool {
189    matches!(t, "base" | "link" | "meta" | "title" | "style" | "script" | "noscript" | "template")
190}
191
192// a start tag that ends an open <p>
193fn 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
202// an element that may lie inside a paragraph read again by itself (Dom::leaf_of): it neither
203// starts blocks or paragraphs nor adds links, images, fields or lists
204fn 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
215// a name of a tag or attribute that can be written into HTML
216fn 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
220// ------------------------------------------------------------------ the tree
221
222impl Dom {
223    /// an empty tree: the root alone
224    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    /// the tree of an HTML text
231    pub(crate) fn parse(s: &str) -> Dom {
232        let mut d = Dom::new();
233        build(&mut d, 0, s);
234        d
235    }
236
237    /// a tree of HTML (a document of the viewer has one: the root exists)
238    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    /// the index of a live node
250    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    // a new node (not in the tree) -> None: the cap
259    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    /// c as the last child of p
310    fn append(&mut self, p: u32, c: u32) {
311        self.insert_before(p, c, NIL);
312    }
313
314    // c (detached) into p before `before` (NIL: at the end)
315    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    // c out of its parent
336    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    /// the nodes of the subtree at i in document order (i first)
361    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    // free the subtree at i (detached first)
376    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            // (the slot's next node gets the next generation: old handles stay stale)
384            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    // the derived block of every node of the subtree at i goes (it moved or left)
391    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    /// i is in the document (the root above it)
398    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    /// i is a or below it
413    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    /// a change: the content (own: false) or the attributes (own: true) of element i
425    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    /// all the text below i
438    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    /// the block element whose one paragraph a change of element e touches (own: e's own
450    /// attributes changed) when that paragraph can be read again by itself: its content is
451    /// formatted text (no links, images, fields, breaks, blocks, lists, raw text), it lies
452    /// outside tables, pre, buttons, lists of choices, labels and links, not too deep for the
453    /// reader's caps -> None: every block again
454    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        // the content: depth below the caps of the reader (NEST_MAX 64 formats)
483        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    /// i is hidden: the hidden attribute
504    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    /// the class attribute of i holds c
509    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    // the HTML of the children of i
514    fn inner_html(&self, i: u32, out: &mut String) {
515        // (node, exit?)
516        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("&amp;"),
571            '<' => out.push_str("&lt;"),
572            '>' => out.push_str("&gt;"),
573            '"' if attr => out.push_str("&quot;"),
574            '\u{A0}' => out.push_str("&nbsp;"),
575            _ => out.push(c),
576        }
577    }
578}
579
580// ------------------------------------------------------------------ building the tree
581
582struct Builder<'a> {
583    d: &'a mut Dom,
584    // the open elements and their part (C_*); st[0] is where the HTML goes (never closed)
585    st: Vec<(u32, u8)>,
586}
587
588impl Builder<'_> {
589    fn top(&self) -> (u32, u8) {
590        self.st[self.st.len() - 1]
591    }
592
593    // the nearest open element `want` takes before one `stop` takes -> its place in the stack
594    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    // text into the open element (in <head> text that is more than blanks ends it)
608    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    // a start tag -> the element (None: the cap of nodes) and whether it reads raw text
631    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        // the head ends at an element of the body
635        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    // an end tag
681    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            // a stray </p>: an empty paragraph
689            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                    // (an inline element's end tag does not close a block)
700                    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
716// the HTML s into node `parent` of d (its children follow the ones it has)
717fn 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    // a raw element just opened: its text up to its end tag
721    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// ------------------------------------------------------------------ selectors
795
796// a compound: a tag ("" any), an id, classes, attributes (name, operator, value)
797#[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/// a selector: its compounds from the subject leftwards; child[i]: compound i + 1 is the parent
806/// (else an ancestor) of compound i's element
807#[derive(Clone, Debug, Default)]
808pub(crate) struct Sel {
809    parts: Vec<Compound>,
810    child: Vec<bool>,
811}
812
813/// Sel::key: what an element needs for the selector to match it at all
814pub(crate) enum SelKey<'a> {
815    Id(&'a str),
816    Class(&'a str),
817    Tag(&'a str),
818    Any,
819}
820
821impl Sel {
822    /// the specificity: ids, classes and attributes, tags (a byte each)
823    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    /// the subject's id, else its first class, else its tag (an index of rules)
834    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
852// an identifier at s[i..] -> (it, the byte after it)
853fn 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
858/// a selector list -> None: something it does not support (it matches nothing)
859pub(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
873// the selectors of a list (commas outside brackets and quotes)
874fn 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    // the combinator before the next compound: 0 none yet, 1 descendant, 2 child
908    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
948// a compound at s[i..] -> (it, the byte after it)
949fn 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                    // (two ids: nothing matches; kept as a class that never occurs)
965                    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
1007// element i takes compound c
1008fn 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
1033/// element i matches selector s (budget: compounds compared at most; 0 left: no match)
1034pub(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    // the run of child combinators from compound k on -> its last compound
1040    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    // compounds k ..= e taken by x, its parent ... -> the element of compound e
1048    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    // the first run at i itself
1065    let e = run(0);
1066    let Some(mut at) = chain(i, 0, e, budget) else { return false };
1067    let mut k = e + 1;
1068    // each further run: at the nearest ancestor that takes it
1069    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
1089// the elements below `top` (the root: the document) a list matches, in document order
1090fn 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
1111// ------------------------------------------------------------------ the API of MdDoc
1112
1113impl 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    /// the document's root (MdNode::NONE for a document from Markdown): its children are the
1123    /// top level of the HTML
1124    pub fn root(&self) -> MdNode {
1125        self.dom.handle(0)
1126    }
1127
1128    /// the live nodes of the tree (out of the document too) and the bytes of their names, texts
1129    /// and attributes; linear in the tree
1130    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    /// the innermost element around byte `byte` of block k's text: blocks know only their own
1136    /// element (MdBlock::node), so this reads the inline content of that element again and lines
1137    /// its characters up with the block's text, blanks aside (linear in the element).  An image's
1138    /// or a field's U+FFFC is its element; a byte at the end, or content that does not line up
1139    /// (hidden by a style sheet, say), gives the block's element; NONE: no block, or none of an
1140    /// element (a document from Markdown)
1141    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        // (the characters of a run of inline content, with their elements)
1148        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                    // the element's other paragraphs and blocks are blocks of their own
1175                    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    /// the first element in the document with this id
1196    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            // (several: the first in document order)
1203            _ => self.dom.subtree(0).into_iter().find(|x| live.contains(x)).map(|i| self.dom.handle(i)),
1204        }
1205    }
1206
1207    /// the elements of the document a selector list matches, in document order (see md_dom.rs;
1208    /// a selector it does not support matches nothing)
1209    pub fn select(&self, sel: &str) -> Vec<MdNode> {
1210        self.select_in(self.root(), sel)
1211    }
1212
1213    /// the first element select(sel) would give
1214    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    /// the elements below n a selector list matches, in document order
1220    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    /// element n matches a selector list
1226    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    /// n's parent (None: the root, a node out of the document, a node that is gone)
1233    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    /// n's children (elements and texts) in order
1240    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    /// the node after n under the same parent
1253    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    /// the node before n under the same parent
1260    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    /// an element's tag in lower case ("" for a text, "#document" for the root)
1267    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    /// n is a text
1275    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    /// n exists (it was not freed)
1280    pub fn alive(&self, n: MdNode) -> bool {
1281        self.node(n).is_some()
1282    }
1283
1284    /// n is in the document
1285    pub fn attached(&self, n: MdNode) -> bool {
1286        self.node(n).is_some_and(|i| self.dom.attached(i))
1287    }
1288
1289    /// an attribute of element n (names in lower case)
1290    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    /// the attributes of element n in order (names in lower case, values decoded)
1296    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    /// element n has class c
1304    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    /// all the text of n (a text: its own; an element: that of its descendants, as textContent)
1309    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    /// the HTML of n's children
1314    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    /// the tree changed since the blocks were derived (update() derives them)
1323    pub fn changed(&self) -> bool {
1324        self.dom.changed
1325    }
1326
1327    // ---- changes
1328
1329    /// set n's text: a text's own; an element's children (freed) become one text
1330    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        // (one text child with this text already: nothing changes)
1343        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    /// set an attribute of element n (the name in lower case; "id" updates the index)
1367    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    /// remove an attribute of element n -> it had one
1392    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    /// add class c to element n
1407    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    /// remove class c from element n -> it had it
1423    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    /// class c on element n off when it has it, else on -> on
1434    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    /// a new element (not in the document: insert puts it there) -> MdNode::NONE: a Markdown
1444    /// document, a tag that is none, the cap of nodes
1445    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    /// a new text (not in the document)
1456    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    /// put n under element `parent` before its child `before` (None: at the end); a node with a
1467    /// parent moves -> false: a node that is gone, the root, n above parent, before no child of
1468    /// parent
1469    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    /// take n out of the document (it stays: insert may put it back; free drops it)
1493    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    /// drop n and its descendants (out of the document first): their handles refer to nothing
1506    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    /// element n's children (freed) become the nodes of an HTML text (raw text elements: the
1520    /// text)
1521    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}