///|
fn document_html_element(root : @dom.Node) -> @dom.Node? {
for child in root.children {
if child.kind == Element && child.name == "html" {
return Some(child)
}
}
None
}
///|
fn append_children_to(target : @dom.Node, children : Array[@dom.Node]) -> Unit {
for child in children {
target.append_child(child)
}
}
///|
/// Flatten `source` into `out`: merge its missing attributes into `target`,
/// detach its children, and push them to `out` in document order, except
/// that nested elements named `name` are flattened the same way in place.
///
/// This is the pre-order walk a recursive implementation would do, driven by
/// an explicit stack of (child list, next index) frames so deeply nested
/// same-named elements cannot overflow the call stack.
fn flatten_same_named_node_contents(
target : @dom.Node,
source : @dom.Node,
name : String,
out : Array[@dom.Node],
) -> Unit {
let frames : Array[(Array[@dom.Node], Ref[Int32])] = []
fn enter(node : @dom.Node) {
merge_missing_attrs(target, node)
let existing = node.children.copy()
node.children.clear()
frames.push((existing, Ref(0)))
}
enter(source)
while frames.last() is Some((existing, next)) {
if next.val >= existing.length() {
ignore(frames.pop())
continue
}
let child = existing[next.val]
next.val += 1
if child.kind == Element && child.name == name {
enter(child)
} else {
out.push(child)
}
}
}
///|
fn append_body_node_contents(
body_node : @dom.Node,
source : @dom.Node,
body_children : Array[@dom.Node],
) -> Unit {
flatten_same_named_node_contents(body_node, source, "body", body_children)
}
///|
fn append_html_node_contents(
html_node : @dom.Node,
source : @dom.Node,
html_children : Array[@dom.Node],
) -> Unit {
flatten_same_named_node_contents(html_node, source, "html", html_children)
}
///|
fn absorb_root_html_siblings(
root : @dom.Node,
html : @dom.Node,
forced_body_nodes : Array[@dom.Node],
after_head_whitespace~ : Array[@dom.Node] = [],
) -> Unit {
let original = root.children.copy()
root.children.clear()
let mut saw_html = false
for child in original {
if child.kind == Element && child.name == "html" {
if saw_html {
append_html_node_contents(html, child, html.children)
} else {
root.append_child(child)
saw_html = true
}
} else if !saw_html &&
child.kind == Text &&
is_whitespace_text_node(child) &&
!nodes_contain(forced_body_nodes, child) {
if nodes_contain(after_head_whitespace, child) {
// Whitespace recorded after moves into the html element
// so the head/body classification places it between them.
child.parent = None
html.append_child(child)
} else {
// Whitespace before the html element is ignored by the
// before-html mode; a document node never holds text.
child.parent = None
}
} else if nodes_contain(forced_body_nodes, child) {
// Content reprocessed in body after a stray /