Skip to main content

base_markdown/
document.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4//! A source-backed arena document and its incremental pull consumer.
5use crate::stream::{Event, EventKind, StreamError, Text};
6use crate::{
7    stream::{Tag, TagKind},
8    NodeSyntax,
9};
10use alloc::{collections::BTreeMap, string::String, vec::Vec};
11use core::{num::NonZeroUsize, ops::Range};
12
13/// A stable index into a document's contiguous node arena.
14#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
16pub struct NodeId(NonZeroUsize);
17
18impl NodeId {
19    fn new(index: usize) -> Self {
20        Self(NonZeroUsize::new(index + 1).expect("an arena index cannot reach usize::MAX"))
21    }
22    fn index(self) -> usize {
23        self.0.get() - 1
24    }
25}
26
27/// A semantic AST node. Child and sibling lists use arena indices, not heap objects.
28#[derive(Clone, Debug)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct Node {
31    /// Node meaning. Container nodes store their start tag.
32    pub kind: EventKind<'static>,
33    /// Source provenance, which remains unchanged when a value is edited.
34    pub span: Range<usize>,
35    parent: Option<NodeId>,
36    first_child: Option<NodeId>,
37    next_sibling: Option<NodeId>,
38}
39
40impl Node {
41    /// Parent node, if any.
42    pub fn parent(&self) -> Option<NodeId> {
43        self.parent
44    }
45    /// First child, if any.
46    pub fn first_child(&self) -> Option<NodeId> {
47        self.first_child
48    }
49    /// Next sibling, if any.
50    pub fn next_sibling(&self) -> Option<NodeId> {
51        self.next_sibling
52    }
53}
54
55/// The result of one bounded AST-building operation.
56#[derive(Clone, Copy, Debug, PartialEq, Eq)]
57pub enum BuildProgress {
58    /// The event budget was consumed; more input may remain.
59    Pending,
60    /// The iterator ended with a balanced document.
61    Complete,
62}
63
64/// An incremental consumer that pulls events directly into an arena.
65#[derive(Debug, Default)]
66pub struct AstBuilder {
67    nodes: Vec<Node>,
68    open: Vec<NodeId>,
69    // Only the active container needs a tail. Opening a child resets it; closing that child makes the child the parent's new tail.
70    last_child: Option<NodeId>,
71    complete: bool,
72    failed: bool,
73}
74
75impl AstBuilder {
76    /// Create an empty builder. It does not borrow or own the parser's source.
77    pub fn new() -> Self {
78        Self::default()
79    }
80
81    /// Reserve arena and open-container capacity for allocation reuse.
82    pub fn with_capacity(nodes: usize, depth: usize) -> Self {
83        Self {
84            nodes: Vec::with_capacity(nodes),
85            open: Vec::with_capacity(depth),
86            ..Self::default()
87        }
88    }
89
90    /// Pull up to `budget` events, retaining construction state between calls.
91    /// A zero budget does no work. Reaching the exact budget does not probe the iterator for another event.
92    ///
93    /// # Errors
94    /// Returns parser errors or [`StreamError::Unbalanced`] for malformed streams. A failed builder cannot be finished.
95    pub fn consume<'a>(
96        &mut self,
97        events: &mut impl Iterator<Item = Result<Event<'a>, StreamError>>,
98        budget: usize,
99    ) -> Result<BuildProgress, StreamError> {
100        if self.failed {
101            return Err(StreamError::Unbalanced);
102        }
103        if self.complete {
104            return Ok(BuildProgress::Complete);
105        }
106        for _ in 0..budget {
107            let event = match events.next() {
108                Some(Ok(event)) => event,
109                Some(Err(error)) => {
110                    self.failed = true;
111                    return Err(error);
112                }
113                None => {
114                    if !self.open.is_empty() || self.nodes.is_empty() {
115                        self.failed = true;
116                        return Err(StreamError::Unbalanced);
117                    }
118                    self.complete = true;
119                    return Ok(BuildProgress::Complete);
120                }
121            };
122            if let Err(error) = self.append(event) {
123                self.failed = true;
124                return Err(error);
125            }
126        }
127        Ok(BuildProgress::Pending)
128    }
129
130    fn append(&mut self, event: Event<'_>) -> Result<(), StreamError> {
131        event.validate(None)?;
132        let event = Event {
133            kind: event.kind.map_text(Text::owned),
134            span: event.span,
135        };
136        if let EventKind::End(kind) = event.kind {
137            let id = self.open.pop().ok_or(StreamError::Unbalanced)?;
138            let node = &mut self.nodes[id.index()];
139            if !matches!(&node.kind, EventKind::Start(tag) if tag.kind() == kind) {
140                return Err(StreamError::Unbalanced);
141            }
142            if event.span.end < node.span.end {
143                return Err(StreamError::InvalidRange);
144            }
145            node.span.end = event.span.end;
146            self.last_child = Some(id);
147            return Ok(());
148        }
149        if self.open.is_empty() {
150            if !self.nodes.is_empty()
151                || !matches!(&event.kind, EventKind::Start(tag) if tag.kind() == crate::stream::TagKind::Document)
152            {
153                return Err(StreamError::Unbalanced);
154            }
155        } else if matches!(&event.kind, EventKind::Start(tag) if tag.kind() == crate::stream::TagKind::Document)
156        {
157            return Err(StreamError::Unbalanced);
158        }
159        if let EventKind::Start(tag) = &event.kind {
160            if matches!(tag.kind(), crate::stream::TagKind::Heading | crate::stream::TagKind::FootnoteDefinition) && self.open.iter().any(|id| {
161                matches!(&self.nodes[id.index()].kind, EventKind::Start(parent) if !parent.kind().allows_descendant(tag.kind()))
162            }) {
163                return Err(StreamError::Unbalanced);
164            }
165        }
166        let id = NodeId::new(self.nodes.len());
167        let parent = self.open.last().copied();
168        let start = matches!(event.kind, EventKind::Start(_));
169        self.nodes.push(Node {
170            kind: event.kind,
171            span: event.span,
172            parent,
173            first_child: None,
174            next_sibling: None,
175        });
176        if let Some(parent) = parent {
177            if let Some(previous) = self.last_child {
178                self.nodes[previous.index()].next_sibling = Some(id);
179            } else {
180                self.nodes[parent.index()].first_child = Some(id);
181            }
182        }
183        self.last_child = if start { None } else { Some(id) };
184        if start {
185            self.open.push(id);
186        }
187        Ok(())
188    }
189
190    /// Attach the original source after the stream has ended. The buffer is moved, not copied.
191    ///
192    /// # Errors
193    /// Returns [`StreamError::Unbalanced`] if consumption failed or has not reached EOF, or [`StreamError::InvalidRange`] for invalid source-backed values.
194    pub fn finish<S: AsRef<str>>(self, source: S) -> Result<Document<S>, StreamError> {
195        if self.failed || !self.complete {
196            return Err(StreamError::Unbalanced);
197        }
198        let document = Document {
199            source,
200            nodes: self.nodes,
201            syntax: BTreeMap::new(),
202        };
203        // Public producers can supply arbitrary offsets. Validate their contract once before exposing the document.
204        for event in document.events() {
205            event
206                .expect("arena traversal cannot fail")
207                .validate(Some(document.source()))?;
208        }
209        Ok(document)
210    }
211
212    /// Number of nodes constructed so far; useful when selecting the next work budget.
213    pub fn len(&self) -> usize {
214        self.nodes.len()
215    }
216    /// Whether no nodes have been constructed.
217    pub fn is_empty(&self) -> bool {
218        self.nodes.is_empty()
219    }
220}
221
222/// An owned source buffer and a direct semantic AST.
223#[derive(Clone, Debug)]
224#[cfg_attr(feature = "serde", derive(serde::Serialize))]
225pub struct Document<S = String> {
226    source: S,
227    nodes: Vec<Node>,
228    syntax: BTreeMap<NodeId, Option<NodeSyntax<'static>>>,
229}
230
231impl<S: AsRef<str>> Document<S> {
232    /// Parse an owned or borrowed UTF-8 buffer into an arena document.
233    ///
234    /// # Errors
235    /// Returns stream validation errors; built-in Markdown parsing has no syntax errors.
236    pub fn parse(source: S, options: &crate::ParseOptions) -> Result<Self, StreamError> {
237        let mut builder = AstBuilder::new();
238        {
239            let mut events = crate::EventParser::new(source.as_ref(), options);
240            if builder.consume(&mut events, usize::MAX).is_err() {
241                unreachable!("the built-in Markdown producer yields a balanced semantic stream");
242            }
243        }
244        builder.finish(source)
245    }
246
247    /// The immutable source buffer.
248    pub fn source(&self) -> &str {
249        self.source.as_ref()
250    }
251    /// All nodes in stable arena order.
252    pub fn nodes(&self) -> &[Node] {
253        &self.nodes
254    }
255    /// The document root. A finished document always has exactly one root.
256    pub fn root(&self) -> NodeId {
257        NodeId::new(0)
258    }
259    /// Access a node by stable index.
260    pub fn node(&self, id: NodeId) -> Option<&Node> {
261        self.nodes.get(id.index())
262    }
263
264    /// Replace a node's semantic value while preserving the arena's structure and provenance.
265    ///
266    /// # Errors
267    /// Rejects invalid source ranges, end events, changing the root identity,
268    /// and replacing a container with children by a leaf.
269    pub fn set_kind(&mut self, id: NodeId, kind: EventKind<'static>) -> Result<(), StreamError> {
270        kind.validate(Some(self.source()))?;
271        let node = self
272            .nodes
273            .get(id.index())
274            .ok_or(StreamError::InvalidRange)?;
275        let document = matches!(&kind, EventKind::Start(Tag::Document(_)));
276        if matches!(&kind, EventKind::End(_))
277            || document != (id == self.root())
278            || (node.first_child.is_some() && !matches!(&kind, EventKind::Start(_)))
279        {
280            return Err(StreamError::Unbalanced);
281        }
282        if let EventKind::Start(tag) = &kind {
283            self.check_capture(node.parent, tag.kind())?;
284            if self.descendants(id).skip(1).any(|child| matches!(&self.nodes[child.index()].kind, EventKind::Start(child_tag) if !tag.kind().allows_descendant(child_tag.kind()))) {
285                return Err(StreamError::Unbalanced);
286            }
287        }
288        self.nodes[id.index()].kind = kind;
289        self.syntax.insert(id, None);
290        self.refresh_context(id);
291        self.refresh_item(self.nodes[id.index()].parent);
292        Ok(())
293    }
294
295    /// Edit the text of a text/code/HTML/opaque leaf. Source-backed values detach on first mutation.
296    ///
297    /// # Errors
298    /// Returns [`StreamError::InvalidRange`] for an invalid node ID or a non-text node.
299    pub fn text_mut(&mut self, id: NodeId) -> Result<&mut String, StreamError> {
300        // Frontmatter's semantic body detaches independently of its retained fence metadata.
301        let body = if matches!(self.node(id), Some(Node {kind:EventKind::Opaque {value:Text::Source(range),..},span,..}) if range==span)
302        {
303            self.frontmatter_value(id).map(String::from)
304        } else {
305            None
306        };
307        let node = self
308            .nodes
309            .get_mut(id.index())
310            .ok_or(StreamError::InvalidRange)?;
311        let (EventKind::Text(text)
312        | EventKind::SoftBreak(text)
313        | EventKind::Math(text)
314        | EventKind::Code { value: text, .. }
315        | EventKind::CodeBlock { value: text, .. }
316        | EventKind::Html { value: text, .. }
317        | EventKind::Opaque { value: text, .. }) = &mut node.kind
318        else {
319            return Err(StreamError::InvalidRange);
320        };
321        if let Some(body) = body {
322            *text = Text::from(body);
323        }
324        text.to_mut(self.source.as_ref())
325    }
326
327    /// Traverse without a heap-allocated traversal stack or cloned owned strings.
328    pub fn events(&self) -> DocumentEvents<'_, S> {
329        DocumentEvents {
330            document: self,
331            current: Some(self.root()),
332            root: self.root(),
333            closing: false,
334        }
335    }
336}
337
338/// A zero-allocation pull traversal over the arena's parent/child/sibling links.
339pub struct DocumentEvents<'a, S> {
340    document: &'a Document<S>,
341    current: Option<NodeId>,
342    root: NodeId,
343    closing: bool,
344}
345
346// Borrow values rather than cloning Cow::Owned strings. Structural metadata has no owned storage.
347fn borrow_kind<'a>(kind: &'a EventKind<'_>) -> EventKind<'a> {
348    match kind {
349        EventKind::Start(tag) => {
350            let tag = match tag {
351                crate::stream::Tag::Link { destination, title } => crate::stream::Tag::Link {
352                    destination: destination.borrowed(),
353                    title: title.as_ref().map(Text::borrowed),
354                },
355                crate::stream::Tag::Image { destination, title } => crate::stream::Tag::Image {
356                    destination: destination.borrowed(),
357                    title: title.as_ref().map(Text::borrowed),
358                },
359                crate::stream::Tag::FootnoteDefinition(value) => {
360                    crate::stream::Tag::FootnoteDefinition(value.borrowed())
361                }
362                _ => tag.clone(),
363            };
364            EventKind::Start(tag)
365        }
366        EventKind::Text(value) => EventKind::Text(value.borrowed()),
367        EventKind::SoftBreak(value) => EventKind::SoftBreak(value.borrowed()),
368        EventKind::Separator { value, conditional } => EventKind::Separator {
369            value: value.borrowed(),
370            conditional: *conditional,
371        },
372        EventKind::Code { value, language } => EventKind::Code {
373            value: value.borrowed(),
374            language: language.as_ref().map(Text::borrowed),
375        },
376        EventKind::CodeBlock {
377            value,
378            language,
379            fence,
380            math,
381            unclosed,
382        } => EventKind::CodeBlock {
383            value: value.borrowed(),
384            language: language.as_ref().map(Text::borrowed),
385            fence: *fence,
386            math: *math,
387            unclosed: *unclosed,
388        },
389        EventKind::Math(value) => EventKind::Math(value.borrowed()),
390        EventKind::Html { value, block } => EventKind::Html {
391            value: value.borrowed(),
392            block: *block,
393        },
394        EventKind::Definition {
395            label,
396            destination,
397            title,
398        } => EventKind::Definition {
399            label: label.borrowed(),
400            destination: destination.borrowed(),
401            title: title.as_ref().map(Text::borrowed),
402        },
403        EventKind::Opaque { value, block } => EventKind::Opaque {
404            value: value.borrowed(),
405            block: *block,
406        },
407        EventKind::FootnoteReference(value) => EventKind::FootnoteReference(value.borrowed()),
408        _ => kind.clone(),
409    }
410}
411
412impl<'a, S: AsRef<str>> Iterator for DocumentEvents<'a, S> {
413    type Item = Result<Event<'a>, StreamError>;
414    fn next(&mut self) -> Option<Self::Item> {
415        let id = self.current?;
416        let node = &self.document.nodes[id.index()];
417        let event = if self.closing {
418            match &node.kind {
419                EventKind::Start(tag) => Event {
420                    kind: EventKind::End(tag.kind()),
421                    span: node.span.end..node.span.end,
422                },
423                _ => unreachable!("only container nodes are closed"),
424            }
425        } else {
426            Event {
427                kind: borrow_kind(&node.kind),
428                span: node.span.clone(),
429            }
430        };
431        if !self.closing && matches!(node.kind, EventKind::Start(_)) {
432            if let Some(child) = node.first_child {
433                self.current = Some(child);
434            } else {
435                self.closing = true;
436            }
437        } else if id == self.root {
438            self.current = None;
439        } else if let Some(sibling) = node.next_sibling {
440            self.current = Some(sibling);
441            self.closing = false;
442        } else {
443            self.current = node.parent;
444            self.closing = true;
445        }
446        Some(Ok(event))
447    }
448}
449
450/// A stack-free iterator over a node's direct children.
451#[derive(Clone)]
452pub struct Children<'a> {
453    nodes: &'a [Node],
454    current: Option<NodeId>,
455}
456impl Iterator for Children<'_> {
457    type Item = NodeId;
458    fn next(&mut self) -> Option<NodeId> {
459        let id = self.current?;
460        self.current = self.nodes[id.index()].next_sibling;
461        Some(id)
462    }
463}
464
465/// A stack-free preorder traversal, including its starting node.
466#[derive(Clone)]
467pub struct Descendants<'a> {
468    nodes: &'a [Node],
469    root: NodeId,
470    current: Option<NodeId>,
471}
472impl Iterator for Descendants<'_> {
473    type Item = NodeId;
474    fn next(&mut self) -> Option<NodeId> {
475        let id = self.current?;
476        self.current = next_descendant(self.nodes, self.root, id);
477        Some(id)
478    }
479}
480
481// Find the next preorder node using the existing links, bounded by the requested subtree.
482fn next_descendant(nodes: &[Node], root: NodeId, id: NodeId) -> Option<NodeId> {
483    if let Some(child) = nodes[id.index()].first_child {
484        return Some(child);
485    }
486    let mut at = id;
487    loop {
488        if at == root {
489            return None;
490        }
491        let node = &nodes[at.index()];
492        if let Some(next) = node.next_sibling {
493            return Some(next);
494        }
495        at = node
496            .parent
497            .expect("a descendant has a parent before its subtree root");
498    }
499}
500
501impl<S: AsRef<str>> Document<S> {
502    /// Direct child IDs in sibling order. Invalid IDs yield an empty iterator.
503    pub fn children(&self, id: NodeId) -> Children<'_> {
504        Children {
505            nodes: &self.nodes,
506            current: self.node(id).and_then(Node::first_child),
507        }
508    }
509    /// Preorder node IDs in a subtree, including `id`. Invalid IDs yield an empty iterator.
510    pub fn descendants(&self, id: NodeId) -> Descendants<'_> {
511        Descendants {
512            nodes: &self.nodes,
513            root: id,
514            current: self.node(id).map(|_| id),
515        }
516    }
517    /// A subtree's events, without surrounding document events unless `id` is the root.
518    pub fn subtree_events(&self, id: NodeId) -> DocumentEvents<'_, S> {
519        DocumentEvents {
520            document: self,
521            current: self.node(id).map(|_| id),
522            root: id,
523            closing: false,
524        }
525    }
526    /// Plain semantic text, including image alternative text, code and raw HTML.
527    pub fn text_content(&self, id: NodeId) -> String {
528        self.collect_text::<false>(id)
529    }
530    fn collect_text<const HEADING: bool>(&self, id: NodeId) -> String {
531        let mut text = String::new();
532        for child in self.descendants(id) {
533            crate::headings::append_text::<HEADING>(
534                &self.nodes[child.index()].kind,
535                self.source(),
536                &mut text,
537            );
538        }
539        text
540    }
541    /// Extract headings and anchors using the HTML writer's rules. Filtered headings still reserve their anchors.
542    pub fn headings(&self, options: &crate::HeadingOptions) -> Vec<crate::HeadingEntry> {
543        let mut anchors = crate::headings::AnchorGenerator::default();
544        let mut entries = Vec::new();
545        for id in self.descendants(self.root()) {
546            if let EventKind::Start(Tag::Heading(level)) = self.nodes[id.index()].kind {
547                let mut text = self.collect_text::<true>(id);
548                text.truncate(crate::util::line_ending::trim_terminal_ending(&text).len());
549                let anchor = anchors.anchor_for(&text);
550                if (options.min_level..=options.max_level).contains(&level) {
551                    entries.push(crate::HeadingEntry {
552                        node: id,
553                        level,
554                        text,
555                        anchor,
556                    });
557                }
558            }
559        }
560        entries
561    }
562    /// Optional authored syntax, inferred lazily from the original source unless explicitly overridden.
563    pub fn syntax(&self, id: NodeId) -> Option<NodeSyntax<'_>> {
564        let node = self.node(id)?;
565        if let Some(value) = self.syntax.get(&id) {
566            return value.as_ref().map(NodeSyntax::borrowed);
567        }
568        if matches!(node.kind, EventKind::Start(Tag::Item { .. })) {
569            let mut previous: Option<NodeId> = None;
570            for child in self
571                .children(id)
572                .filter(|id| !matches!(self.nodes[id.index()].kind, EventKind::Separator { .. }))
573            {
574                if let Some(previous) = previous {
575                    let end = self.nodes[previous.index()].span.end;
576                    let start = self.nodes[child.index()].span.start;
577                    if let Some(gap) = self.source().get(end..start) {
578                        let ended = self
579                            .source()
580                            .as_bytes()
581                            .get(end.wrapping_sub(1))
582                            .is_some_and(|byte| matches!(byte, b'\r' | b'\n'));
583                        if crate::util::line_ending::line_endings(gap)
584                            .nth(usize::from(!ended))
585                            .is_some()
586                        {
587                            return Some(NodeSyntax::ListItem { spread: true });
588                        }
589                    }
590                }
591                previous = Some(child);
592            }
593            return Some(NodeSyntax::ListItem { spread: false });
594        }
595        crate::node_syntax::infer(self.source(), node.span.clone(), &node.kind)
596    }
597    /// Override authored syntax. `None` disconnects the node from inferred source syntax.
598    ///
599    /// # Errors
600    /// Rejects invalid node IDs, source ranges and metadata for an incompatible node kind.
601    pub fn set_syntax(
602        &mut self,
603        id: NodeId,
604        syntax: Option<NodeSyntax<'static>>,
605    ) -> Result<(), StreamError> {
606        let node = self.node(id).ok_or(StreamError::InvalidRange)?;
607        if syntax
608            .as_ref()
609            .is_some_and(|value| !value.valid_for(&node.kind, self.source()))
610        {
611            return Err(StreamError::InvalidRange);
612        }
613        self.syntax.insert(id, syntax);
614        Ok(())
615    }
616    /// Frontmatter's body without its opening and closing fences. Edited bodies are returned directly.
617    pub fn frontmatter_value(&self, id: NodeId) -> Option<&str> {
618        let node = self.node(id)?;
619        let EventKind::Opaque { value: text, .. } = &node.kind else {
620            return None;
621        };
622        if !matches!(self.syntax(id), Some(NodeSyntax::Frontmatter { .. })) {
623            return None;
624        }
625        let value = text
626            .resolve(self.source())
627            .expect("document text is validated");
628        if let Text::Source(range) = text {
629            if *range == node.span {
630                if let Some((_, _, _, body)) = crate::node_syntax::frontmatter(value) {
631                    return value.get(body);
632                }
633            }
634        }
635        Some(value)
636    }
637    /// Render the whole document through the shared event HTML writer.
638    ///
639    /// # Errors
640    /// Propagates output writer errors.
641    pub fn render_html(
642        &self,
643        options: &crate::CompileOptions,
644        output: &mut impl core::fmt::Write,
645    ) -> Result<(), StreamError> {
646        crate::render_events(self.source(), self.events(), options, output)
647    }
648    /// Render one subtree through the shared event HTML writer.
649    ///
650    /// # Errors
651    /// Rejects invalid node IDs and propagates output writer errors.
652    pub fn render_node(
653        &self,
654        id: NodeId,
655        options: &crate::CompileOptions,
656        output: &mut impl core::fmt::Write,
657    ) -> Result<(), StreamError> {
658        if self.node(id).is_none() {
659            return Err(StreamError::InvalidRange);
660        }
661        if id == self.root() {
662            return self.render_html(options, output);
663        }
664        let start = Event {
665            kind: EventKind::Start(Tag::Document(None)),
666            span: 0..0,
667        };
668        let end = Event {
669            kind: EventKind::End(TagKind::Document),
670            span: self.source().len()..self.source().len(),
671        };
672        crate::render_events(
673            self.source(),
674            core::iter::once(Ok(start))
675                .chain(self.subtree_events(id))
676                .chain(core::iter::once(Ok(end))),
677            options,
678            output,
679        )
680    }
681    fn tight_paragraph(&self, id: NodeId) -> bool {
682        if !matches!(
683            self.nodes[id.index()].kind,
684            EventKind::Start(Tag::Paragraph { .. })
685        ) {
686            return false;
687        }
688        let Some(parent) = self.nodes[id.index()].parent else {
689            return false;
690        };
691        if !matches!(
692            self.nodes[parent.index()].kind,
693            EventKind::Start(Tag::Item { .. })
694        ) {
695            return false;
696        }
697        self.nodes[parent.index()].parent.is_some_and(|list| {
698            matches!(
699                self.nodes[list.index()].kind,
700                EventKind::Start(Tag::List { tight: true, .. })
701            )
702        })
703    }
704    fn refresh_item(&mut self, id: Option<NodeId>) {
705        let Some(id) = id else {
706            return;
707        };
708        if !matches!(
709            self.nodes[id.index()].kind,
710            EventKind::Start(Tag::Item { .. })
711        ) {
712            return;
713        }
714        let last = self
715            .children(id)
716            .filter(|child| {
717                !matches!(
718                    self.nodes[child.index()].kind,
719                    EventKind::Separator { .. }
720                        | EventKind::Definition { .. }
721                        | EventKind::Opaque { .. }
722                        | EventKind::Start(Tag::FootnoteDefinition(_))
723                )
724            })
725            .last();
726        let block_end = last.is_some_and(|last| !self.tight_paragraph(last));
727        self.nodes[id.index()].kind = EventKind::Start(Tag::Item { block_end });
728    }
729    // Edits can change the context of existing paragraphs and item endings. Refresh those rendering hints without adding caches or links to every arena node.
730    fn refresh_context(&mut self, root: NodeId) {
731        let mut current = Some(root);
732        while let Some(id) = current {
733            current = next_descendant(&self.nodes, root, id);
734            if matches!(
735                self.nodes[id.index()].kind,
736                EventKind::Start(Tag::Paragraph { .. })
737            ) {
738                self.nodes[id.index()].kind = EventKind::Start(Tag::Paragraph {
739                    tight: self.tight_paragraph(id),
740                });
741            }
742            self.refresh_item(Some(id));
743        }
744    }
745    fn check_capture(&self, mut parent: Option<NodeId>, kind: TagKind) -> Result<(), StreamError> {
746        while let Some(id) = parent {
747            let node = &self.nodes[id.index()];
748            if matches!(&node.kind,EventKind::Start(tag) if !tag.kind().allows_descendant(kind)) {
749                return Err(StreamError::Unbalanced);
750            }
751            parent = node.parent;
752        }
753        Ok(())
754    }
755    /// Append a new semantic node. Its source provenance is empty; values may still reference the source buffer.
756    ///
757    /// # Errors
758    /// Rejects invalid parents, document/end kinds, source ranges, or forbidden nested capture containers.
759    pub fn append_child(
760        &mut self,
761        parent: NodeId,
762        kind: EventKind<'static>,
763    ) -> Result<NodeId, StreamError> {
764        kind.validate(Some(self.source()))?;
765        if !matches!(
766            self.node(parent).map(|node| &node.kind),
767            Some(EventKind::Start(_))
768        ) {
769            return Err(StreamError::Unbalanced);
770        }
771        match &kind {
772            EventKind::End(_) | EventKind::Start(Tag::Document(_)) => {
773                return Err(StreamError::Unbalanced)
774            }
775            EventKind::Start(tag) => self.check_capture(Some(parent), tag.kind())?,
776            _ => {}
777        }
778        let previous = self.children(parent).last();
779        let id = self.insert_child(parent, kind, previous, None);
780        self.refresh_context(id);
781        self.refresh_item(Some(parent));
782        Ok(id)
783    }
784    // Callers validate the semantic content and insertion position before changing links, then refresh context once the operation is complete.
785    fn link_child(
786        &mut self,
787        parent: NodeId,
788        id: NodeId,
789        previous: Option<NodeId>,
790        next: Option<NodeId>,
791    ) {
792        self.nodes[id.index()].parent = Some(parent);
793        self.nodes[id.index()].next_sibling = next;
794        if let Some(previous) = previous {
795            self.nodes[previous.index()].next_sibling = Some(id);
796        } else {
797            self.nodes[parent.index()].first_child = Some(id);
798        }
799    }
800    fn insert_child(
801        &mut self,
802        parent: NodeId,
803        kind: EventKind<'static>,
804        previous: Option<NodeId>,
805        next: Option<NodeId>,
806    ) -> NodeId {
807        let id = NodeId::new(self.nodes.len());
808        self.nodes.push(Node {
809            kind,
810            span: 0..0,
811            parent: None,
812            first_child: None,
813            next_sibling: None,
814        });
815        self.link_child(parent, id, previous, next);
816        id
817    }
818    /// Detach a subtree without moving its arena slots or invalidating IDs.
819    ///
820    /// # Errors
821    /// Rejects invalid IDs and detaching the document root.
822    pub fn detach(&mut self, id: NodeId) -> Result<(), StreamError> {
823        if id == self.root() {
824            return Err(StreamError::Unbalanced);
825        }
826        let node = self.node(id).ok_or(StreamError::InvalidRange)?;
827        let Some(parent) = node.parent else {
828            return Ok(());
829        };
830        self.unlink_child(id, parent);
831        self.refresh_context(id);
832        self.refresh_item(Some(parent));
833        Ok(())
834    }
835    fn unlink_child(&mut self, id: NodeId, parent: NodeId) {
836        let next = self.nodes[id.index()].next_sibling;
837        if self.nodes[parent.index()].first_child == Some(id) {
838            self.nodes[parent.index()].first_child = next;
839        } else {
840            let previous = self
841                .children(parent)
842                .find(|child| self.nodes[child.index()].next_sibling == Some(id))
843                .expect("an attached node occurs in its parent's child chain");
844            self.nodes[previous.index()].next_sibling = next;
845        }
846        self.nodes[id.index()].parent = None;
847        self.nodes[id.index()].next_sibling = None;
848    }
849    /// Move an existing subtree into a container, optionally before one of its children. No node or text allocation is required.
850    ///
851    /// # Errors
852    /// Rejects cycles, invalid IDs/positions, root moves, and forbidden capture nesting before modifying the tree.
853    pub fn attach(
854        &mut self,
855        parent: NodeId,
856        id: NodeId,
857        before: Option<NodeId>,
858    ) -> Result<(), StreamError> {
859        if id == self.root()
860            || !matches!(
861                self.node(parent).map(|node| &node.kind),
862                Some(EventKind::Start(_))
863            )
864        {
865            return Err(StreamError::Unbalanced);
866        }
867        self.node(id).ok_or(StreamError::InvalidRange)?;
868        if before.is_some_and(|before| {
869            self.node(before)
870                .map_or(true, |node| node.parent != Some(parent))
871        }) {
872            return Err(StreamError::InvalidRange);
873        }
874        let mut ancestor = Some(parent);
875        while let Some(current) = ancestor {
876            if current == id {
877                return Err(StreamError::Unbalanced);
878            }
879            ancestor = self.nodes[current.index()].parent;
880        }
881        for child in self.descendants(id) {
882            if let EventKind::Start(tag) = &self.nodes[child.index()].kind {
883                self.check_capture(Some(parent), tag.kind())?;
884            }
885        }
886        if before == Some(id) {
887            return Ok(());
888        }
889        let former_parent = self.nodes[id.index()].parent;
890        if let Some(parent) = former_parent {
891            self.unlink_child(id, parent);
892        }
893        let previous = self
894            .children(parent)
895            .take_while(|child| Some(*child) != before)
896            .last();
897        self.link_child(parent, id, previous, before);
898        self.refresh_context(id);
899        if former_parent != Some(parent) {
900            self.refresh_item(former_parent);
901        }
902        self.refresh_item(Some(parent));
903        Ok(())
904    }
905    /// Detach all direct children, returning their stable IDs in order.
906    ///
907    /// # Errors
908    /// Rejects invalid node IDs.
909    pub fn extract_children(&mut self, parent: NodeId) -> Result<Vec<NodeId>, StreamError> {
910        self.node(parent).ok_or(StreamError::InvalidRange)?;
911        let children: Vec<_> = self.children(parent).collect();
912        self.nodes[parent.index()].first_child = None;
913        for &id in &children {
914            self.nodes[id.index()].parent = None;
915            self.nodes[id.index()].next_sibling = None;
916            self.refresh_context(id);
917        }
918        self.refresh_item(Some(parent));
919        Ok(children)
920    }
921    /// Replace a heading section with another document's root children, materializing their source-backed values into this document.
922    ///
923    /// `heading_index` counts direct children, including separator nodes. With `remove_subsections`, the section extends through headings deeper than the selected heading. Detached original nodes retain their IDs and can still be inspected or reattached.
924    ///
925    /// # Errors
926    /// Rejects invalid parent IDs. Returns `false` when the selected child is not a heading.
927    pub fn replace_heading_section<T: AsRef<str>>(
928        &mut self,
929        parent: NodeId,
930        heading_index: usize,
931        replacement: &Document<T>,
932        replace_heading: bool,
933        remove_subsections: bool,
934    ) -> Result<bool, StreamError> {
935        self.node(parent).ok_or(StreamError::InvalidRange)?;
936        let mut children = self.children(parent);
937        let mut previous = children.by_ref().take(heading_index).last();
938        let Some(heading) = children.next() else {
939            return Ok(false);
940        };
941        let EventKind::Start(Tag::Heading(level)) = self.nodes[heading.index()].kind else {
942            return Ok(false);
943        };
944        let first = if replace_heading {
945            Some(heading)
946        } else {
947            self.nodes[heading.index()].next_sibling
948        };
949        let mut end = self.nodes[heading.index()].next_sibling;
950        while let Some(id) = end {
951            if matches!(self.nodes[id.index()].kind,EventKind::Start(Tag::Heading(next)) if !remove_subsections || next<=level)
952            {
953                break;
954            }
955            end = self.nodes[id.index()].next_sibling;
956        }
957        // Selecting an existing heading proves this parent and all its ancestors accept heading/footnote captures. Every copied subtree is already validated in its source document.
958        if !replace_heading {
959            previous = Some(heading);
960        }
961        // A section is one contiguous sibling range: splice it out once, retaining stable IDs and independent detached subtrees.
962        if first != end {
963            if let Some(previous) = previous {
964                self.nodes[previous.index()].next_sibling = end;
965            } else {
966                self.nodes[parent.index()].first_child = end;
967            }
968            let mut at = first;
969            while at != end {
970                let id = at.expect("the section ends in the selected sibling chain");
971                at = self.nodes[id.index()].next_sibling;
972                self.nodes[id.index()].parent = None;
973                self.nodes[id.index()].next_sibling = None;
974                self.refresh_context(id);
975            }
976            self.refresh_item(Some(parent));
977        }
978        for child in replacement.children(replacement.root()) {
979            previous = Some(self.copy_subtree(replacement, child, parent, previous, end));
980        }
981        Ok(true)
982    }
983    fn copy_subtree<T: AsRef<str>>(
984        &mut self,
985        source: &Document<T>,
986        root: NodeId,
987        mut parent: NodeId,
988        mut previous: Option<NodeId>,
989        before: Option<NodeId>,
990    ) -> NodeId {
991        let copied_root = NodeId::new(self.nodes.len());
992        let mut original_parent = source.nodes[root.index()].parent;
993        for original in source.descendants(root) {
994            let node = &source.nodes[original.index()];
995            // Source and destination parent links describe the same copied path; ascending them together needs no mapping stack.
996            while original_parent != node.parent {
997                previous = Some(parent);
998                parent = self.nodes[parent.index()]
999                    .parent
1000                    .expect("a copied descendant has a copied parent");
1001                original_parent = source.nodes[original_parent
1002                    .expect("subtree traversal stays below its original parent")
1003                    .index()]
1004                .parent;
1005            }
1006            let body = source.frontmatter_value(original);
1007            let kind = borrow_kind(&node.kind).map_text(|value| {
1008                body.map_or_else(
1009                    || value.materialized(source.source()),
1010                    |body| Text::from(String::from(body)),
1011                )
1012            });
1013            let next = if original == root { before } else { None };
1014            let id = self.insert_child(parent, kind, previous, next);
1015            if let Some(syntax) = source
1016                .syntax(original)
1017                .and_then(|syntax| syntax.copied(source.source()))
1018            {
1019                self.set_syntax(id, Some(syntax))
1020                    .expect("materialized syntax describes the copied node kind");
1021            }
1022            if node.first_child.is_some() {
1023                original_parent = Some(original);
1024                parent = id;
1025                previous = None;
1026            } else {
1027                previous = Some(id);
1028            }
1029        }
1030        self.refresh_context(copied_root);
1031        self.refresh_item(self.nodes[copied_root.index()].parent);
1032        copied_root
1033    }
1034}
1035
1036#[cfg(feature = "serde")]
1037impl<'de> serde::Deserialize<'de> for Document<String> {
1038    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
1039        #[derive(serde::Deserialize)]
1040        struct Stored {
1041            source: String,
1042            nodes: Vec<Node>,
1043            #[serde(default)]
1044            syntax: BTreeMap<NodeId, Option<NodeSyntax<'static>>>,
1045        }
1046        let stored = Stored::deserialize(deserializer)?;
1047        let document = Self {
1048            source: stored.source,
1049            nodes: stored.nodes,
1050            syntax: stored.syntax,
1051        };
1052        document
1053            .validate_storage()
1054            .map_err(serde::de::Error::custom)?;
1055        Ok(document)
1056    }
1057}
1058
1059#[cfg(feature = "serde")]
1060impl<S: AsRef<str>> Document<S> {
1061    // Validate the complete arena forest, including detached nodes, before public traversal can observe deserialized links. Each slot must be visited exactly once.
1062    fn validate_storage(&self) -> Result<(), StreamError> {
1063        if !matches!(
1064            self.nodes.first().map(|node| &node.kind),
1065            Some(EventKind::Start(Tag::Document(_)))
1066        ) {
1067            return Err(StreamError::Unbalanced);
1068        }
1069        if self.nodes[0].parent.is_some() || self.nodes[0].next_sibling.is_some() {
1070            return Err(StreamError::Unbalanced);
1071        }
1072        let mut seen = alloc::vec![false;self.nodes.len()];
1073        let mut pending = Vec::new();
1074        for (index, node) in self.nodes.iter().enumerate() {
1075            if node.parent.is_none() {
1076                if node.next_sibling.is_some() {
1077                    return Err(StreamError::Unbalanced);
1078                }
1079                pending.push((NodeId::new(index), None, false));
1080            }
1081        }
1082        while let Some((id, parent, capture)) = pending.pop() {
1083            let node = self.node(id).ok_or(StreamError::InvalidRange)?;
1084            if seen[id.index()] || node.parent != parent {
1085                return Err(StreamError::Unbalanced);
1086            }
1087            seen[id.index()] = true;
1088            Event {
1089                kind: borrow_kind(&node.kind),
1090                span: node.span.clone(),
1091            }
1092            .validate(Some(self.source()))?;
1093            let container = matches!(node.kind, EventKind::Start(_));
1094            if matches!(node.kind, EventKind::End(_))
1095                || (node.first_child.is_some() && !container)
1096                || matches!(node.kind, EventKind::Start(Tag::Document(_))) != (id == self.root())
1097            {
1098                return Err(StreamError::Unbalanced);
1099            }
1100            if capture
1101                && matches!(
1102                    node.kind,
1103                    EventKind::Start(Tag::Heading(_) | Tag::FootnoteDefinition(_))
1104                )
1105            {
1106                return Err(StreamError::Unbalanced);
1107            }
1108            if let Some(next) = node.next_sibling {
1109                pending.push((next, parent, capture));
1110            }
1111            if let Some(child) = node.first_child {
1112                let capture = capture
1113                    || matches!(
1114                        node.kind,
1115                        EventKind::Start(Tag::Heading(_) | Tag::Image { .. })
1116                    );
1117                pending.push((child, Some(id), capture));
1118            }
1119        }
1120        if seen.iter().any(|visited| !*visited) {
1121            return Err(StreamError::Unbalanced);
1122        }
1123        for (&id, value) in &self.syntax {
1124            let node = self.node(id).ok_or(StreamError::InvalidRange)?;
1125            if value
1126                .as_ref()
1127                .is_some_and(|value| !value.valid_for(&node.kind, self.source()))
1128            {
1129                return Err(StreamError::InvalidRange);
1130            }
1131        }
1132        Ok(())
1133    }
1134}